Riteesh2k6 commited on
Commit
c86a973
·
verified ·
1 Parent(s): 2b6c522

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +861 -852
src/streamlit_app.py CHANGED
@@ -1,852 +1,861 @@
1
- import streamlit as st
2
- import pandas as pd
3
- import json
4
- import os
5
- import uuid
6
- from datetime import datetime
7
- from collections import Counter
8
- import base64
9
- import glob
10
-
11
- # --- Page Configuration ---
12
- st.set_page_config(
13
- page_title="రుచి చూడు (Ruchi Chudu)",
14
- page_icon="🍲",
15
- layout="wide"
16
- )
17
-
18
- # --- Data Storage Functions ---
19
- RECIPES_FOLDER = "recipes"
20
- IMAGES_FOLDER = "recipe_images"
21
-
22
- def ensure_folders_exist():
23
- """Create necessary folders if they don't exist"""
24
- os.makedirs(RECIPES_FOLDER, exist_ok=True)
25
- os.makedirs(IMAGES_FOLDER, exist_ok=True)
26
-
27
- def generate_recipe_id():
28
- """Generate a unique ID for each recipe"""
29
- return str(uuid.uuid4())[:8]
30
-
31
- def save_image(image_file, recipe_id):
32
- """Save uploaded image to images folder"""
33
- if image_file is not None:
34
- try:
35
- ensure_folders_exist()
36
- # Get file extension
37
- file_extension = image_file.name.split('.')[-1].lower()
38
- image_filename = f"{recipe_id}.{file_extension}"
39
- image_path = os.path.join(IMAGES_FOLDER, image_filename)
40
-
41
- # Save image file
42
- with open(image_path, "wb") as f:
43
- f.write(image_file.getvalue())
44
-
45
- return image_filename
46
- except Exception as e:
47
- st.error(f"Error saving image: {e}")
48
- return None
49
- return None
50
-
51
- def load_image(image_filename):
52
- """Load image from images folder"""
53
- if image_filename:
54
- try:
55
- image_path = os.path.join(IMAGES_FOLDER, image_filename)
56
- if os.path.exists(image_path):
57
- with open(image_path, "rb") as f:
58
- return f.read()
59
- except Exception as e:
60
- st.error(f"Error loading image: {e}")
61
- return None
62
-
63
- def save_recipe(recipe_data):
64
- """Save individual recipe to its own JSON file"""
65
- try:
66
- ensure_folders_exist()
67
- recipe_id = recipe_data['id']
68
- filename = f"recipe_{recipe_id}.json"
69
- filepath = os.path.join(RECIPES_FOLDER, filename)
70
-
71
- with open(filepath, 'w', encoding='utf-8') as f:
72
- json.dump(recipe_data, f, ensure_ascii=False, indent=2)
73
- return True
74
- except Exception as e:
75
- st.error(f"Error saving recipe: {e}")
76
- return False
77
-
78
- def load_all_recipes():
79
- """Load all recipes from individual JSON files"""
80
- recipes = []
81
- try:
82
- ensure_folders_exist()
83
- # Get all recipe JSON files
84
- recipe_files = glob.glob(os.path.join(RECIPES_FOLDER, "recipe_*.json"))
85
-
86
- for filepath in recipe_files:
87
- try:
88
- with open(filepath, 'r', encoding='utf-8') as f:
89
- recipe = json.load(f)
90
- recipes.append(recipe)
91
- except Exception as e:
92
- st.error(f"Error loading recipe from {filepath}: {e}")
93
- continue
94
-
95
- # Sort by submission date (newest first)
96
- recipes.sort(key=lambda x: x.get('submitted_date', ''), reverse=True)
97
-
98
- except Exception as e:
99
- st.error(f"Error loading recipes: {e}")
100
-
101
- return recipes
102
-
103
- def delete_recipe(recipe_id):
104
- """Delete a recipe and its associated image"""
105
- try:
106
- # Delete recipe JSON file
107
- recipe_filename = f"recipe_{recipe_id}.json"
108
- recipe_filepath = os.path.join(RECIPES_FOLDER, recipe_filename)
109
- if os.path.exists(recipe_filepath):
110
- os.remove(recipe_filepath)
111
-
112
- # Delete associated image files (check all common extensions)
113
- for ext in ['jpg', 'jpeg', 'png', 'gif']:
114
- image_filename = f"{recipe_id}.{ext}"
115
- image_filepath = os.path.join(IMAGES_FOLDER, image_filename)
116
- if os.path.exists(image_filepath):
117
- os.remove(image_filepath)
118
- break
119
-
120
- return True
121
- except Exception as e:
122
- st.error(f"Error deleting recipe: {e}")
123
- return False
124
-
125
- def get_recipe_stats():
126
- """Get statistics about stored recipes"""
127
- try:
128
- recipe_files = glob.glob(os.path.join(RECIPES_FOLDER, "recipe_*.json"))
129
- image_files = glob.glob(os.path.join(IMAGES_FOLDER, "*.*"))
130
-
131
- return {
132
- 'total_recipes': len(recipe_files),
133
- 'total_images': len(image_files),
134
- 'storage_size': sum(os.path.getsize(f) for f in recipe_files + image_files if os.path.exists(f))
135
- }
136
- except:
137
- return {'total_recipes': 0, 'total_images': 0, 'storage_size': 0}
138
-
139
- # --- Language and Text ---
140
- TEXT = {
141
- "en": {
142
- "title": "Ruchi Chudu",
143
- "subtitle": "A Community Cookbook to Preserve Telugu Cuisine",
144
- "language_select": "Choose Language",
145
- "sidebar_header": "Contribute Your Recipe!",
146
- "tab_submit": "📝 Submit Your Recipe",
147
- "tab_gallery": "🍲 Community Gallery",
148
- "tab_analytics": "📊 Recipe Analytics",
149
- "tab_manage": "⚙️ Manage Recipes",
150
- "form_header": "Tell us about your dish",
151
- "dish_name": "Dish Name",
152
- "your_name": "Your Name",
153
- "district": "Your District",
154
- "select_district": "--- Select a District ---",
155
- "recipe_type": "Recipe Type (e.g., Curry, Pickle, Snack)",
156
- "ingredients": "Ingredients (one per line)",
157
- "instructions": "Instructions",
158
- "story": "The Story Behind Your Dish (your memories, family traditions, etc.)",
159
- "image_upload": "Upload a photo of the dish (optional)",
160
- "submit_button": "Submit Recipe",
161
- "success_message": "Thank you! Your recipe has been submitted successfully.",
162
- "gallery_header": "Explore Recipes from the Community",
163
- "gallery_empty": "No recipes submitted yet. Be the first to share!",
164
- "submitted_by": "Submitted by",
165
- "from_district": "from",
166
- "submitted_on": "Submitted on",
167
- "recipe_id": "Recipe ID",
168
- "story_title": "📖 The Story",
169
- "ingredients_title": "🌶️ Ingredients",
170
- "instructions_title": "✍️ Instructions",
171
- "tags_title": "AI-Generated Tags",
172
- "error_dish_name": "Please enter a dish name before submitting.",
173
- "search_placeholder": "Search recipes...",
174
- "filter_by_district": "Filter by District",
175
- "filter_by_type": "Filter by Recipe Type",
176
- "all_districts": "All Districts",
177
- "all_types": "All Recipe Types",
178
- "total_recipes": "Total Recipes",
179
- "total_images": "Total Images",
180
- "storage_size": "Storage Used",
181
- "top_districts": "Top Contributing Districts",
182
- "popular_types": "Popular Recipe Types",
183
- "recent_recipes": "Recent Submissions",
184
- "clear_filters": "Clear All Filters",
185
- "export_recipes": "Export All Recipes",
186
- "manage_header": "Recipe Management",
187
- "delete_recipe": "Delete Recipe",
188
- "confirm_delete": "Are you sure you want to delete this recipe?",
189
- "recipe_deleted": "Recipe deleted successfully!",
190
- "backup_data": "Backup All Data",
191
- "backup_created": "Backup created successfully!"
192
- },
193
- "te": {
194
- "title": "చి చూడు",
195
- "subtitle": "మన తెలుగు వంటల స్కృతినిాపాడుదాం",
196
- "language_select": " చుకోండి",
197
- "sidebar_header": "మీ వంటకాన్ని పంపండి!",
198
- "tab_submit": "📝 మీ వంటకాననిండి",
199
- "tab_gallery": "🍲 కమ్యూనిట ్యాలరీ",
200
- "tab_analytics": "📊 వటకాలిశ్లేషణ",
201
- "tab_manage": "⚙️ వంటకాలను నిర్వహిచండి",
202
- "form_header": "మీ వంటకం గురించి చెప్పండి",
203
- "dish_name": "వంటకం పేరు",
204
- "your_name": "మీ పేరు",
205
- "district": "మీ జిల్లా",
206
- "select_district": "--- ిల్లాను ఎచుకోండి ---",
207
- "recipe_type": "వంటకం రకం (ఉదా. కూ, పచ్చడి, )",
208
- "ingredients": "కావలసినవి (ఒకానికి ఒకటి)",
209
- "instructions": "తయారీ ిధానం",
210
- "story": "ఈ వంటతో మీ కథ (మీ జ్ఞాపకాు, కుటుంబ సంప్రయాలు మొదైనవి)",
211
- "image_upload": "వంటకం ఫోటోను అపోడ్ చేయండి (ఐచ్ఛికం)",
212
- "submit_button": "వంటకాన్ని పంపండి",
213
- "success_message": "న్యవాదాలు! మీ వంటకం విజయవంతంగసమ్పించబడింది.",
214
- "gallery_header": "మ్యూనిటీ నుడి వంటకాల అన్వేషించండి",
215
- "gallery_empty": "ఇంకా వంటకాలు సపిచబడలేదు. పంుకున్న మొదటి వ్యక్ి మీరే అవవండి!",
216
- "submitted_by": "సమర్పించివా",
217
- "from_district": "నుి",
218
- "submitted_on": "సమర్పిిన తేదీ",
219
- "recipe_id": "వంకం ID",
220
- "story_title": "📖",
221
- "ingredients_title": "🌶️ కాసిి",
222
- "instructions_title": "✍️ తయారీ ిధాన",
223
- "tags_title": "AI ద్వారా రూపందించడిన ట్యాగ��‌లు",
224
- "error_dish_name": "మర్పించే మందు దయేసి వంటకం పేరుమోదుేయడి.",
225
- "search_placeholder": "వంటకాలనుండి...",
226
- "filter_by_district": "జిలలా ద్వరా ఫిల్టర్ చేయండి",
227
- "filter_by_type": "వంకం రకం ద్వారా ఫిల్టర్ చేయడి",
228
- "all_districts": "్ని ిల్లాలు",
229
- "all_types": "అన్ని వంటకాల రకాలు",
230
- "total_recipes": "మొత్తం వంటకాలు",
231
- "total_images": "మొత్తం చిత్రాలు",
232
- "storage_size": "వాడిన స్టోరేజ్",
233
- "top_districts": "అధిక సహకారం అందించిన జిల్లాలు",
234
- "popular_types": "ప్రాచుర్యం పొందిన వంటకాల రకాలు",
235
- "recent_recipes": "ఇటీవలి సమర్పణలు",
236
- "clear_filters": "అన్ని ఫిల్టర్లను క్లియర్ చేయండి",
237
- "export_recipes": "అన్ని వంటకాలను ఎగుమతి చేయండి",
238
- "manage_header": "వంటకాల నిర్వహణ",
239
- "delete_recipe": "వంటకాన్ని తొలగించండి",
240
- "confirm_delete": "మీరు ఖచ్చితంగా వంటకాన్ని తొలగించాలనుకుంటున్నారా?",
241
- "recipe_deleted": "వంటకం విజయవంతంగా తొలగించబడింది!",
242
- "backup_data": "అన్ని డేటాను బ్యాకప్ చేయండి",
243
- "backup_created": "బ్యాకప్ విజయవంతంగా సృష్టించబడింది!"
244
- }
245
- }
246
-
247
- # --- Data for Dropdowns ---
248
- DISTRICTS = [
249
- "Adilabad", "Bhadradri Kothagudem", "Hanumakonda", "Hyderabad", "Jagtial", "Jangaon",
250
- "Jayashankar Bhupalpally", "Jogulamba Gadwal", "Kamareddy", "Karimnagar", "Khammam",
251
- "Komaram Bheem", "Mahabubabad", "Mahbubnagar", "Mancherial", "Medak", "Medchal-Malkajgiri",
252
- "Mulugu", "Nagarkurnool", "Nalgonda", "Narayanpet", "Nirmal", "Nizamabad", "Peddapalli",
253
- "Rajanna Sircilla", "Ranga Reddy", "Sangareddy", "Siddipet", "Suryapet", "Vikarabad",
254
- "Wanaparthy", "Warangal", "Yadadri Bhuvanagiri", "Alluri Sitharama Raju", "Anakapalli",
255
- "Anantapur", "Annamayya", "Bapatla", "Chittoor", "East Godavari", "Eluru", "Guntur",
256
- "Kakinada", "Konaseema", "Krishna", "Kurnool", "Nandyal", "NTR", "Palnadu", "Parvathipuram Manyam",
257
- "Prakasam", "Sri Potti Sriramulu Nellore", "Sri Sathya Sai", "Srikakulam", "Tirupati",
258
- "Visakhapatnam", "Vizianagaram", "West Godavari", "YSR Kadapa"
259
- ]
260
- DISTRICTS.sort()
261
-
262
- RECIPE_TYPES = ["Curry (కూర)", "Fry (వేపుడు)", "Pickle (పచ్చడి)", "Chutney (చట్నీ)", "Pulusu (పులుసు)", "Snack (చిరుతిండి)", "Sweet (తీపి)", "Breakfast (అల్పాహారం)", "Rice Dish (అన్నం రకం)", "Other (ఇతర)"]
263
-
264
- # --- Session State Initialization ---
265
- if 'language' not in st.session_state:
266
- st.session_state['language'] = 'en'
267
- if 'recipes' not in st.session_state:
268
- st.session_state['recipes'] = load_all_recipes()
269
- if 'refresh_recipes' not in st.session_state:
270
- st.session_state['refresh_recipes'] = False
271
-
272
- # Refresh recipes if needed
273
- if st.session_state.get('refresh_recipes', False):
274
- st.session_state['recipes'] = load_all_recipes()
275
- st.session_state['refresh_recipes'] = False
276
-
277
- # --- Helper Functions ---
278
- def get_text(key):
279
- """Fetches text from the dictionary based on the selected language."""
280
- return TEXT[st.session_state['language']][key]
281
-
282
- def simulate_ai_tagging(story, ingredients):
283
- """Simulate AI tagging based on content analysis"""
284
- tags = set()
285
- text_to_scan = (story + " " + ingredients).lower()
286
-
287
- keyword_map = {
288
- "festival": ["sankranti", "ugadi", "dasara", "diwali", "vinayaka chaviti"],
289
- "spicy": ["chilli", "karam", "mirapa", "pepper", "guntur"],
290
- "healthy": ["millet", "ragi", "jowar", "vegetable", "leafy", "greens"],
291
- "quick": ["15 min", "20 min", "quick", "easy", "tvaraga"],
292
- "traditional": ["grandma", "ammamma", "nanamma", "traditional", "sampradayam"],
293
- "summer": ["mango", "summer", "cool", "curd", "perugu"],
294
- "winter": ["winter", "warm", "hot", "sheeta"],
295
- "vegetarian": ["vegetable", "veg", "sabzi", "kura"],
296
- "non-vegetarian": ["chicken", "mutton", "fish", "egg", "kodi", "meka"]
297
- }
298
-
299
- for tag, keywords in keyword_map.items():
300
- if any(keyword in text_to_scan for keyword in keywords):
301
- tags.add(tag.capitalize())
302
-
303
- if not tags:
304
- tags.add("Community Recipe")
305
-
306
- return list(tags)
307
-
308
- def filter_recipes(recipes, search_term="", district_filter="", type_filter=""):
309
- """Filter recipes based on search and filter criteria"""
310
- filtered = recipes
311
-
312
- if search_term:
313
- filtered = [r for r in filtered if
314
- search_term.lower() in r['dish_name'].lower() or
315
- search_term.lower() in r.get('your_name', '').lower() or
316
- search_term.lower() in r.get('ingredients', '').lower()]
317
-
318
- if district_filter and district_filter != get_text('all_districts'):
319
- filtered = [r for r in filtered if r.get('district') == district_filter]
320
-
321
- if type_filter and type_filter != get_text('all_types'):
322
- filtered = [r for r in filtered if r.get('recipe_type') == type_filter]
323
-
324
- return filtered
325
-
326
- def export_recipes_to_csv(recipes):
327
- """Export recipes to CSV format"""
328
- if not recipes:
329
- return None
330
-
331
- export_data = []
332
- for recipe in recipes:
333
- export_data.append({
334
- 'Recipe ID': recipe.get('id', ''),
335
- 'Dish Name': recipe['dish_name'],
336
- 'Submitted By': recipe.get('your_name', ''),
337
- 'District': recipe.get('district', ''),
338
- 'Recipe Type': recipe.get('recipe_type', ''),
339
- 'Ingredients': recipe.get('ingredients', ''),
340
- 'Instructions': recipe.get('instructions', ''),
341
- 'Story': recipe.get('story', ''),
342
- 'Tags': ', '.join(recipe.get('tags', [])),
343
- 'Submitted Date': recipe.get('submitted_date', ''),
344
- 'Image File': recipe.get('image_filename', '')
345
- })
346
-
347
- return pd.DataFrame(export_data)
348
-
349
- def format_storage_size(size_bytes):
350
- """Convert bytes to human readable format"""
351
- for unit in ['B', 'KB', 'MB', 'GB']:
352
- if size_bytes < 1024.0:
353
- return f"{size_bytes:.1f} {unit}"
354
- size_bytes /= 1024.0
355
- return f"{size_bytes:.1f} TB"
356
-
357
- # --- Sidebar ---
358
- with st.sidebar:
359
- st.title(f"🍲 {get_text('title')}")
360
- st.markdown(get_text('subtitle'))
361
- st.markdown("---")
362
-
363
- # Language selector
364
- lang_choice = st.radio(
365
- get_text('language_select'),
366
- ('English', 'తెలుగు'),
367
- horizontal=True,
368
- key='lang_radio'
369
- )
370
- st.session_state['language'] = 'te' if lang_choice == 'తెలుగు' else 'en'
371
-
372
- st.info(get_text('sidebar_header'))
373
-
374
- # Storage stats
375
- stats = get_recipe_stats()
376
- st.metric(get_text('total_recipes'), stats['total_recipes'])
377
- st.metric(get_text('total_images'), stats['total_images'])
378
- st.metric(get_text('storage_size'), format_storage_size(stats['storage_size']))
379
-
380
- # --- Main App Layout ---
381
- st.title(get_text('title'))
382
-
383
- tab_submit, tab_gallery, tab_analytics, tab_manage = st.tabs([
384
- get_text('tab_submit'),
385
- get_text('tab_gallery'),
386
- get_text('tab_analytics'),
387
- get_text('tab_manage')
388
- ])
389
-
390
- # --- Submission Form Tab ---
391
- with tab_submit:
392
- st.header(get_text('form_header'))
393
-
394
- with st.form(key="recipe_form"):
395
- col1, col2 = st.columns(2)
396
-
397
- with col1:
398
- dish_name = st.text_input(label=get_text('dish_name'))
399
- your_name = st.text_input(label=get_text('your_name'))
400
- image_file = st.file_uploader(get_text('image_upload'), type=['jpg', 'jpeg', 'png'])
401
-
402
- with col2:
403
- recipe_type = st.selectbox(label=get_text('recipe_type'), options=RECIPE_TYPES)
404
- district = st.selectbox(
405
- label=get_text('district'),
406
- options=[get_text('select_district')] + DISTRICTS
407
- )
408
-
409
- ingredients = st.text_area(label=get_text('ingredients'), height=150)
410
- instructions = st.text_area(label=get_text('instructions'), height=200)
411
- story = st.text_area(label=get_text('story'), height=150)
412
-
413
- submitted = st.form_submit_button(get_text('submit_button'))
414
-
415
- if submitted:
416
- if not dish_name:
417
- st.error(get_text('error_dish_name'))
418
- else:
419
- # Generate unique ID for this recipe
420
- recipe_id = generate_recipe_id()
421
-
422
- # Save image if provided
423
- image_filename = save_image(image_file, recipe_id)
424
-
425
- # Generate AI tags
426
- ai_tags = simulate_ai_tagging(story, ingredients)
427
-
428
- # Create new recipe with unique ID
429
- new_recipe = {
430
- "id": recipe_id,
431
- "dish_name": dish_name,
432
- "your_name": your_name,
433
- "district": district if district != get_text('select_district') else '',
434
- "recipe_type": recipe_type,
435
- "ingredients": ingredients,
436
- "instructions": instructions,
437
- "story": story,
438
- "image_filename": image_filename,
439
- "tags": ai_tags,
440
- "submitted_date": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
441
- }
442
-
443
- # Save recipe to individual JSON file
444
- if save_recipe(new_recipe):
445
- # Add to session state for immediate display
446
- st.session_state.recipes.insert(0, new_recipe)
447
- st.success(f"{get_text('success_message')} (ID: {recipe_id})")
448
- st.balloons()
449
-
450
- # Show recipe details
451
- with st.expander("Recipe Details", expanded=True):
452
- st.write(f"**{get_text('recipe_id')}:** `{recipe_id}`")
453
- st.write(f"**File Location:** `{RECIPES_FOLDER}/recipe_{recipe_id}.json`")
454
- if image_filename:
455
- st.write(f"**Image Location:** `{IMAGES_FOLDER}/{image_filename}`")
456
- else:
457
- st.error("Failed to save recipe. Please try again.")
458
-
459
- # --- Community Gallery Tab ---
460
- with tab_gallery:
461
- st.header(get_text('gallery_header'))
462
-
463
- # Refresh button
464
- if st.button("🔄 Refresh Recipes"):
465
- st.session_state['refresh_recipes'] = True
466
- st.rerun()
467
-
468
- # Filters and search
469
- col1, col2, col3, col4 = st.columns([2, 1, 1, 1])
470
-
471
- with col1:
472
- search_term = st.text_input(
473
- label="",
474
- placeholder=get_text('search_placeholder'),
475
- key="search_recipes"
476
- )
477
-
478
- with col2:
479
- district_options = [get_text('all_districts')] + DISTRICTS
480
- district_filter = st.selectbox(
481
- get_text('filter_by_district'),
482
- district_options,
483
- key="district_filter"
484
- )
485
-
486
- with col3:
487
- type_options = [get_text('all_types')] + RECIPE_TYPES
488
- type_filter = st.selectbox(
489
- get_text('filter_by_type'),
490
- type_options,
491
- key="type_filter"
492
- )
493
-
494
- with col4:
495
- if st.button(get_text('clear_filters')):
496
- st.session_state.search_recipes = ""
497
- st.session_state.district_filter = get_text('all_districts')
498
- st.session_state.type_filter = get_text('all_types')
499
- st.rerun()
500
-
501
- st.markdown("---")
502
-
503
- # Filter recipes
504
- filtered_recipes = filter_recipes(
505
- st.session_state.recipes,
506
- search_term,
507
- district_filter,
508
- type_filter
509
- )
510
-
511
- if not filtered_recipes:
512
- if not st.session_state.recipes:
513
- st.info(get_text('gallery_empty'))
514
- else:
515
- st.info("No recipes match your search criteria. Try adjusting your filters.")
516
- else:
517
- st.write(f"**{len(filtered_recipes)}** recipes found")
518
-
519
- # Display recipes
520
- for recipe in filtered_recipes:
521
- with st.container():
522
- st.markdown("""
523
- <style>
524
- .recipe-card {
525
- background-color: #f8f9fa;
526
- border-radius: 15px;
527
- padding: 25px;
528
- margin-bottom: 25px;
529
- border-left: 5px solid #ff6b6b;
530
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
531
- }
532
- </style>
533
- """, unsafe_allow_html=True)
534
-
535
- with st.container():
536
- st.markdown('<div class="recipe-card">', unsafe_allow_html=True)
537
-
538
- # Header with recipe ID
539
- col_header1, col_header2, col_header3 = st.columns([2, 1, 1])
540
- with col_header1:
541
- st.subheader(f"🍽️ {recipe['dish_name']}")
542
- st.code(f"ID: {recipe.get('id', 'N/A')}", language=None)
543
-
544
- with col_header2:
545
- if recipe.get('recipe_type'):
546
- st.info(recipe['recipe_type'])
547
-
548
- with col_header3:
549
- if recipe.get('submitted_date'):
550
- st.caption(f"{get_text('submitted_on')}: {recipe['submitted_date'][:10]}")
551
-
552
- # Metadata
553
- metadata_parts = []
554
- if recipe.get('your_name'):
555
- metadata_parts.append(f"**{recipe['your_name']}**")
556
- if recipe.get('district'):
557
- metadata_parts.append(f"{get_text('from_district')} **{recipe['district']}**")
558
-
559
- if metadata_parts:
560
- st.caption(f"{get_text('submitted_by')} " + " • ".join(metadata_parts))
561
-
562
- # Tags
563
- if recipe.get('tags'):
564
- tag_display = " ".join([f"`{tag}`" for tag in recipe['tags']])
565
- st.markdown(tag_display)
566
-
567
- # Content
568
- left_col, right_col = st.columns([1, 1.2])
569
-
570
- with left_col:
571
- if recipe.get('image_filename'):
572
- try:
573
- image_data = load_image(recipe['image_filename'])
574
- if image_data:
575
- st.image(image_data, caption=recipe['dish_name'], use_column_width=True)
576
- else:
577
- st.image("https://placehold.co/400x300/E8F4FD/31343C?text=Image+Not+Found")
578
- except:
579
- st.image("https://placehold.co/400x300/E8F4FD/31343C?text=Error+Loading+Image")
580
- else:
581
- st.image("https://placehold.co/400x300/E8F4FD/31343C?text=No+Image+Available")
582
-
583
- with right_col:
584
- if recipe.get('story'):
585
- with st.expander(get_text('story_title')):
586
- st.write(recipe['story'])
587
-
588
- with st.expander(get_text('ingredients_title')):
589
- if recipe.get('ingredients'):
590
- ingredients_list = recipe['ingredients'].split('\n')
591
- for ingredient in ingredients_list:
592
- if ingredient.strip():
593
- st.write(f"• {ingredient.strip()}")
594
- else:
595
- st.write("No ingredients listed.")
596
-
597
- with st.expander(get_text('instructions_title'), expanded=True):
598
- st.write(recipe.get('instructions', 'No instructions provided.'))
599
-
600
- st.markdown('</div>', unsafe_allow_html=True)
601
-
602
- st.markdown("---")
603
-
604
- # --- Analytics Tab ---
605
- with tab_analytics:
606
- st.header("📊 Recipe Analytics")
607
-
608
- if not st.session_state.recipes:
609
- st.info("No data available yet. Submit some recipes to see analytics!")
610
- else:
611
- # Overview metrics
612
- col1, col2, col3, col4 = st.columns(4)
613
-
614
- stats = get_recipe_stats()
615
-
616
- with col1:
617
- st.metric(get_text('total_recipes'), len(st.session_state.recipes))
618
-
619
- with col2:
620
- unique_contributors = len(set(r.get('your_name', 'Anonymous') for r in st.session_state.recipes if r.get('your_name')))
621
- st.metric("Contributors", unique_contributors)
622
-
623
- with col3:
624
- unique_districts = len(set(r.get('district', '') for r in st.session_state.recipes if r.get('district')))
625
- st.metric("Districts Represented", unique_districts)
626
-
627
- with col4:
628
- recipes_with_images = len([r for r in st.session_state.recipes if r.get('image_filename')])
629
- st.metric("Recipes with Photos", recipes_with_images)
630
-
631
- st.markdown("---")
632
-
633
- # Storage information
634
- st.subheader("📁 Storage Information")
635
- col1, col2, col3 = st.columns(3)
636
-
637
- with col1:
638
- st.info(f"**Recipe Files:** {stats['total_recipes']}")
639
- st.caption(f"Location: `{RECIPES_FOLDER}/`")
640
-
641
- with col2:
642
- st.info(f"**Image Files:** {stats['total_images']}")
643
- st.caption(f"Location: `{IMAGES_FOLDER}/`")
644
-
645
- with col3:
646
- st.info(f"**Total Storage:** {format_storage_size(stats['storage_size'])}")
647
-
648
- st.markdown("---")
649
-
650
- # Charts
651
- col1, col2 = st.columns(2)
652
-
653
- with col1:
654
- st.subheader(get_text('top_districts'))
655
- district_counts = Counter(r.get('district', 'Unknown') for r in st.session_state.recipes if r.get('district'))
656
- if district_counts:
657
- district_df = pd.DataFrame(list(district_counts.items()), columns=['District', 'Count'])
658
- district_df = district_df.sort_values('Count', ascending=True).tail(10)
659
- st.bar_chart(district_df.set_index('District'))
660
- else:
661
- st.info("No district data available")
662
-
663
- with col2:
664
- st.subheader(get_text('popular_types'))
665
- type_counts = Counter(r.get('recipe_type', 'Unknown') for r in st.session_state.recipes)
666
- if type_counts:
667
- type_df = pd.DataFrame(list(type_counts.items()), columns=['Type', 'Count'])
668
- st.bar_chart(type_df.set_index('Type'))
669
-
670
- # Recent activity
671
- st.subheader(get_text('recent_recipes'))
672
- recent_recipes = sorted(st.session_state.recipes,
673
- key=lambda x: x.get('submitted_date', ''),
674
- reverse=True)[:10]
675
-
676
- for recipe in recent_recipes:
677
- col1, col2, col3, col4 = st.columns([2, 1, 1, 1])
678
- with col1:
679
- st.write(f"**{recipe['dish_name']}**")
680
- with col2:
681
- st.write(recipe.get('your_name', 'Anonymous'))
682
- with col3:
683
- st.write(recipe.get('submitted_date', 'Unknown')[:10] if recipe.get('submitted_date') else 'Unknown')
684
- with col4:
685
- st.code(recipe.get('id', 'N/A'), language=None)
686
-
687
- # Export functionality
688
- st.markdown("---")
689
- col1, col2 = st.columns(2)
690
-
691
- with col1:
692
- if st.button(get_text('export_recipes')):
693
- csv_data = export_recipes_to_csv(st.session_state.recipes)
694
- if csv_data is not None:
695
- csv_string = csv_data.to_csv(index=False)
696
- st.download_button(
697
- label="Download CSV",
698
- data=csv_string,
699
- file_name=f"ruchi_chudu_recipes_{datetime.now().strftime('%Y%m%d')}.csv",
700
- mime="text/csv"
701
- )
702
- else:
703
- st.error("No recipes to export")
704
-
705
- with col2:
706
- if st.button(get_text('backup_data')):
707
- try:
708
- import zipfile
709
- from io import BytesIO
710
-
711
- # Create a zip file containing all recipe files and images
712
- zip_buffer = BytesIO()
713
- with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
714
- # Add all recipe JSON files
715
- recipe_files = glob.glob(os.path.join(RECIPES_FOLDER, "recipe_*.json"))
716
- for recipe_file in recipe_files:
717
- zip_file.write(recipe_file, os.path.basename(recipe_file))
718
-
719
- # Add all image files
720
- image_files = glob.glob(os.path.join(IMAGES_FOLDER, "*.*"))
721
- for image_file in image_files:
722
- zip_file.write(image_file, f"images/{os.path.basename(image_file)}")
723
-
724
- zip_buffer.seek(0)
725
-
726
- st.download_button(
727
- label="Download Backup ZIP",
728
- data=zip_buffer.getvalue(),
729
- file_name=f"ruchi_chudu_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip",
730
- mime="application/zip"
731
- )
732
- st.success(get_text('backup_created'))
733
- except Exception as e:
734
- st.error(f"Error creating backup: {e}")
735
-
736
- # --- Management Tab ---
737
- with tab_manage:
738
- st.header(get_text('manage_header'))
739
-
740
- if not st.session_state.recipes:
741
- st.info("No recipes to manage yet.")
742
- else:
743
- st.write(f"Managing **{len(st.session_state.recipes)}** recipes")
744
-
745
- # Search for specific recipe to manage
746
- search_manage = st.text_input("Search for recipe to manage", placeholder="Enter recipe name or ID...")
747
-
748
- # Filter recipes for management
749
- manage_recipes = st.session_state.recipes
750
- if search_manage:
751
- manage_recipes = [r for r in st.session_state.recipes if
752
- search_manage.lower() in r['dish_name'].lower() or
753
- search_manage.lower() in r.get('id', '').lower()]
754
-
755
- if not manage_recipes and search_manage:
756
- st.warning("No recipes found matching your search.")
757
-
758
- # Display recipes for management
759
- for recipe in manage_recipes[:20]: # Limit to 20 for performance
760
- with st.expander(f"🍽️ {recipe['dish_name']} (ID: {recipe.get('id', 'N/A')})"):
761
- col1, col2, col3 = st.columns([2, 1, 1])
762
-
763
- with col1:
764
- st.write(f"**Submitted by:** {recipe.get('your_name', 'Anonymous')}")
765
- st.write(f"**District:** {recipe.get('district', 'Not specified')}")
766
- st.write(f"**Type:** {recipe.get('recipe_type', 'Not specified')}")
767
- st.write(f"**Submitted:** {recipe.get('submitted_date', 'Unknown')}")
768
-
769
- with col2:
770
- st.write(f"**Recipe File:**")
771
- st.code(f"recipe_{recipe.get('id', 'unknown')}.json")
772
-
773
- if recipe.get('image_filename'):
774
- st.write(f"**Image File:**")
775
- st.code(recipe['image_filename'])
776
-
777
- with col3:
778
- # Delete button with confirmation
779
- if st.button(f"🗑️ {get_text('delete_recipe')}", key=f"delete_{recipe.get('id', 'unknown')}"):
780
- if st.session_state.get(f"confirm_delete_{recipe.get('id')}", False):
781
- if delete_recipe(recipe.get('id')):
782
- st.success(get_text('recipe_deleted'))
783
- st.session_state['refresh_recipes'] = True
784
- st.rerun()
785
- else:
786
- st.error("Failed to delete recipe.")
787
- else:
788
- st.session_state[f"confirm_delete_{recipe.get('id')}"] = True
789
- st.warning(get_text('confirm_delete'))
790
- st.button(f"✅ Confirm Delete", key=f"confirm_{recipe.get('id')}")
791
-
792
- if len(manage_recipes) > 20:
793
- st.info(f"Showing first 20 recipes. Use search to find specific recipes. Total: {len(manage_recipes)}")
794
-
795
- # Bulk operations
796
- st.markdown("---")
797
- st.subheader("🔧 Bulk Operations")
798
-
799
- col1, col2, col3 = st.columns(3)
800
-
801
- with col1:
802
- if st.button("📊 Recount Storage Stats"):
803
- stats = get_recipe_stats()
804
- st.success("Storage stats updated!")
805
-
806
- with col2:
807
- if st.button("🔄 Refresh All Data"):
808
- st.session_state['refresh_recipes'] = True
809
- st.success("Data will be refreshed!")
810
-
811
- with col3:
812
- if st.button("🧹 Clean Orphaned Files"):
813
- try:
814
- # Find orphaned image files (images without corresponding recipes)
815
- recipe_ids = set(r.get('id') for r in st.session_state.recipes if r.get('id'))
816
- image_files = glob.glob(os.path.join(IMAGES_FOLDER, "*.*"))
817
-
818
- orphaned_count = 0
819
- for image_file in image_files:
820
- image_name = os.path.basename(image_file)
821
- image_id = image_name.split('.')[0] # Get ID from filename
822
-
823
- if image_id not in recipe_ids:
824
- os.remove(image_file)
825
- orphaned_count += 1
826
-
827
- st.success(f"Cleaned {orphaned_count} orphaned files!")
828
- except Exception as e:
829
- st.error(f"Error cleaning files: {e}")
830
-
831
- # --- File Structure Display ---
832
- st.sidebar.markdown("---")
833
- st.sidebar.subheader("📁 File Structure")
834
- if st.sidebar.button("Show File Structure"):
835
- with st.sidebar.expander("Current Structure", expanded=True):
836
- try:
837
- recipe_files = glob.glob(os.path.join(RECIPES_FOLDER, "*.json"))
838
- image_files = glob.glob(os.path.join(IMAGES_FOLDER, "*.*"))
839
-
840
- st.write("📂 recipes/")
841
- for recipe_file in sorted(recipe_files)[:5]: # Show first 5
842
- st.write(f" 📄 {os.path.basename(recipe_file)}")
843
- if len(recipe_files) > 5:
844
- st.write(f" ... and {len(recipe_files) - 5} more")
845
-
846
- st.write("📂 recipe_images/")
847
- for image_file in sorted(image_files)[:5]: # Show first 5
848
- st.write(f" 🖼️ {os.path.basename(image_file)}")
849
- if len(image_files) > 5:
850
- st.write(f" ... and {len(image_files) - 5} more")
851
- except:
852
- st.write("Error reading file structure")
 
 
 
 
 
 
 
 
 
 
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(
11
+ page_title="రుచి చూడు (Ruchi Chudu)",
12
+ page_icon="🍲",
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)",
139
+ "ingredients": "Ingredients (one per line)",
140
+ "instructions": "Instructions",
141
+ "story": "The Story Behind Your Dish (your memories, family traditions, etc.)",
142
+ "image_upload": "Upload a photo of the dish (optional)",
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
+
232
+ # --- Data for Dropdowns ---
233
+ DISTRICTS = [
234
+ "Adilabad", "Bhadradri Kothagudem", "Hanumakonda", "Hyderabad", "Jagtial", "Jangaon",
235
+ "Jayashankar Bhupalpally", "Jogulamba Gadwal", "Kamareddy", "Karimnagar", "Khammam",
236
+ "Komaram Bheem", "Mahabubabad", "Mahbubnagar", "Mancherial", "Medak", "Medchal-Malkajgiri",
237
+ "Mulugu", "Nagarkurnool", "Nalgonda", "Narayanpet", "Nirmal", "Nizamabad", "Peddapalli",
238
+ "Rajanna Sircilla", "Ranga Reddy", "Sangareddy", "Siddipet", "Suryapet", "Vikarabad",
239
+ "Wanaparthy", "Warangal", "Yadadri Bhuvanagiri", "Alluri Sitharama Raju", "Anakapalli",
240
+ "Anantapur", "Annamayya", "Bapatla", "Chittoor", "East Godavari", "Eluru", "Guntur",
241
+ "Kakinada", "Konaseema", "Krishna", "Kurnool", "Nandyal", "NTR", "Palnadu", "Parvathipuram Manyam",
242
+ "Prakasam", "Sri Potti Sriramulu Nellore", "Sri Sathya Sai", "Srikakulam", "Tirupati",
243
+ "Visakhapatnam", "Vizianagaram", "West Godavari", "YSR Kadapa"
244
+ ]
245
+ DISTRICTS.sort()
246
+
247
+ RECIPE_TYPES = ["Curry (కూర)", "Fry (వేపుడు)", "Pickle (పచ్చడి)", "Chutney (చట్నీ)", "Pulusu (పులుసు)", "Snack (చిరుతిండి)", "Sweet (తీపి)", "Breakfast (అల్పాహారం)", "Rice Dish (అన్నం రకం)", "Other (ఇతర)"]
248
+
249
+ # --- Session State Initialization ---
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
344
+ lang_choice = st.radio(
345
+ get_text('language_select'),
346
+ ('English', 'తెలుగు'),
347
+ horizontal=True,
348
+ key='lang_radio'
349
+ )
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
+
377
+ with st.form(key="recipe_form"):
378
+ col1, col2 = st.columns(2)
379
+
380
+ with col1:
381
+ dish_name = st.text_input(label=get_text('dish_name'))
382
+ your_name = st.text_input(label=get_text('your_name'))
383
+ image_file = st.file_uploader(get_text('image_upload'), type=['jpg', 'jpeg', 'png'])
384
+
385
+ with col2:
386
+ recipe_type = st.selectbox(label=get_text('recipe_type'), options=RECIPE_TYPES)
387
+ district = st.selectbox(
388
+ label=get_text('district'),
389
+ options=[get_text('select_district')] + DISTRICTS
390
+ )
391
+
392
+ ingredients = st.text_area(label=get_text('ingredients'), height=150)
393
+ instructions = st.text_area(label=get_text('instructions'), height=200)
394
+ story = st.text_area(label=get_text('story'), height=150)
395
+
396
+ submitted = st.form_submit_button(get_text('submit_button'))
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,
416
+ "district": district if district != get_text('select_district') else '',
417
+ "recipe_type": recipe_type,
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}")