Riteesh2k6 commited on
Commit
d80e4e9
·
verified ·
1 Parent(s): 894663a

Upload 2 files

Browse files
Files changed (2) hide show
  1. src/app.py +852 -0
  2. src/recipes/recipe_60ee3415.json +15 -0
src/app.py ADDED
@@ -0,0 +1,852 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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")
src/recipes/recipe_60ee3415.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "60ee3415",
3
+ "dish_name": "123",
4
+ "your_name": "ehhhht",
5
+ "district": "",
6
+ "recipe_type": "Curry (కూర)",
7
+ "ingredients": "tthtehtehtht",
8
+ "instructions": "ththetreye",
9
+ "story": "etrthtrhtht",
10
+ "image_filename": null,
11
+ "tags": [
12
+ "Community Recipe"
13
+ ],
14
+ "submitted_date": "2025-07-24 13:44:38"
15
+ }