rairo commited on
Commit
6518a48
Β·
verified Β·
1 Parent(s): cb14d51

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +146 -58
app.py CHANGED
@@ -46,37 +46,67 @@ def parse_numbered_steps(text):
46
  steps = re.findall(r"\n\s*(\d+).\s*(.*)", text, re.MULTILINE)
47
  return [(int(num), desc.strip()) for num, desc in steps]
48
 
49
- # 1.5 Enhanced File Upload Handler
50
  def handle_uploaded_file(uploaded_file):
51
- """Enhanced file handler with better error handling and validation."""
52
  if uploaded_file is None:
53
- return None
54
 
55
  try:
56
- # Validate file size (limit to 10MB)
57
- if uploaded_file.size > 10 * 1024 * 1024:
58
- st.error("File size too large. Please upload an image smaller than 10MB.")
59
- return None
 
 
 
 
 
 
 
60
 
61
- # Validate file type
62
- if not uploaded_file.type.startswith('image/'):
63
- st.error("Please upload a valid image file (JPG, PNG, etc.)")
64
- return None
65
 
66
- # Try to open the image to validate it's not corrupted
67
  try:
68
- image = Image.open(uploaded_file)
69
- image.verify() # Verify it's a valid image
70
- uploaded_file.seek(0) # Reset file pointer after verification
71
- image = Image.open(uploaded_file) # Reopen for actual use
72
- return image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  except Exception as img_error:
74
- st.error(f"Invalid image file: {str(img_error)}")
75
- return None
76
 
77
  except Exception as e:
78
- st.error(f"Error processing uploaded file: {str(e)}")
79
- return None
80
 
81
  # 1.6 TTS Generation Function with better error handling
82
  @st.cache_data
@@ -130,7 +160,7 @@ if "app_state" not in st.session_state:
130
  "prompt_sent": False, "timer_running": {}, "last_tick": {},
131
  "project_title": "", "project_description": "", "upcycling_options": [],
132
  "plan_approved": False, "initial_plan": "", "user_image": None,
133
- "upload_error": None, "upload_attempts": 0
134
  }
135
 
136
  # ─────────────────────────────────────────────────────────────────────────────
@@ -145,7 +175,7 @@ def reset_state():
145
  "prompt_sent": False, "timer_running": {}, "last_tick": {},
146
  "project_title": "", "project_description": "", "upcycling_options": [],
147
  "plan_approved": False, "initial_plan": "", "user_image": None,
148
- "upload_error": None, "upload_attempts": 0
149
  }
150
  st.success("βœ… Reset complete!")
151
  st.rerun()
@@ -349,7 +379,7 @@ def render_step(idx, text):
349
  st.rerun()
350
 
351
  # ─────────────────────────────────────────────────────────────────────────────
352
- # 4. APP LAYOUT
353
  # ─────────────────────────────────────────────────────────────────────────────
354
 
355
  st.set_page_config(page_title="NeoFix DIY Assistant", page_icon="πŸ› οΈ", layout="wide")
@@ -369,37 +399,66 @@ if not st.session_state.app_state['prompt_sent']:
369
  col1, col2 = st.columns([3, 1])
370
 
371
  with col1:
372
- # Enhanced file uploader with better error handling
373
  st.markdown("### πŸ“· Upload Project Image")
374
 
375
- # Show upload attempts if there have been errors
 
 
 
376
  if st.session_state.app_state.get('upload_attempts', 0) > 0:
377
  st.info(f"Upload attempts: {st.session_state.app_state['upload_attempts']}")
378
 
379
- # File uploader with enhanced parameters
 
380
  uploaded_image = st.file_uploader(
381
- "Upload a photo of your project",
382
  type=["jpg", "jpeg", "png", "bmp", "gif"],
383
  accept_multiple_files=False,
384
- help="Supported formats: JPG, PNG, BMP, GIF. Max size: 10MB"
 
385
  )
386
 
387
- # Alternative upload method using camera
388
- if not uploaded_image:
389
- st.markdown("##### Or take a photo:")
390
- camera_image = st.camera_input("Take a picture")
391
- if camera_image:
392
- uploaded_image = camera_image
393
 
394
- # Show image preview if uploaded
395
- if uploaded_image:
396
- processed_image = handle_uploaded_file(uploaded_image)
397
- if processed_image:
398
- st.image(processed_image, caption="Uploaded image preview", use_container_width=True)
399
- st.success("βœ… Image uploaded successfully!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  else:
401
- st.session_state.app_state['upload_attempts'] += 1
402
- uploaded_image = None # Reset if processing failed
403
 
404
  context_text = st.text_area(
405
  "✏️ Describe the issue or your goal (optional but recommended)",
@@ -410,33 +469,62 @@ if not st.session_state.app_state['prompt_sent']:
410
  with col2:
411
  st.markdown("### Actions")
412
 
413
- # Enhanced Get AI Guidance button
414
- if st.button("πŸš€ Get AI Guidance", type="primary", use_container_width=True):
 
 
 
 
 
 
 
 
 
 
415
  if uploaded_image:
416
- processed_image = handle_uploaded_file(uploaded_image)
417
- if processed_image:
418
- initial_analysis(processed_image, context_text)
419
- st.rerun()
420
- else:
421
- st.error("❌ Please upload a valid image file!")
 
422
  else:
 
 
 
 
 
423
  st.warning("⚠️ Please upload an image first!")
 
 
424
 
425
  # Troubleshooting section
426
  with st.expander("πŸ”§ Upload Troubleshooting"):
427
  st.markdown("""
428
- **If uploads aren't working:**
429
- 1. Try refreshing the page (Ctrl+F5)
430
- 2. Check file size (max 10MB)
431
- 3. Try a different image format
432
- 4. Use the camera option instead
433
- 5. Clear your browser cache
434
  """)
435
 
436
- if st.button("πŸ”„ Force Refresh Upload", use_container_width=True):
437
  st.session_state.app_state['upload_attempts'] = 0
438
  st.session_state.app_state['upload_error'] = None
 
439
  st.rerun()
 
 
 
 
 
 
 
 
 
 
440
 
441
  if st.button("πŸ”„ Start Over", use_container_width=True):
442
  reset_state()
 
46
  steps = re.findall(r"\n\s*(\d+).\s*(.*)", text, re.MULTILINE)
47
  return [(int(num), desc.strip()) for num, desc in steps]
48
 
49
+ # 1.5 FIXED File Upload Handler
50
  def handle_uploaded_file(uploaded_file):
51
+ """Enhanced file handler with better error handling and validation for Hugging Face Spaces."""
52
  if uploaded_file is None:
53
+ return None, "No file uploaded"
54
 
55
  try:
56
+ # Get file info
57
+ file_details = {
58
+ "filename": uploaded_file.name,
59
+ "filetype": uploaded_file.type,
60
+ "filesize": uploaded_file.size
61
+ }
62
+
63
+ # Validate file size (limit to 5MB for better performance in HF Spaces)
64
+ max_size = 5 * 1024 * 1024 # 5MB
65
+ if uploaded_file.size > max_size:
66
+ return None, f"File size ({uploaded_file.size / 1024 / 1024:.1f}MB) exceeds limit (5MB)"
67
 
68
+ # Validate file type more strictly
69
+ allowed_types = ['image/jpeg', 'image/jpg', 'image/png', 'image/bmp', 'image/gif']
70
+ if uploaded_file.type not in allowed_types:
71
+ return None, f"Unsupported file type: {uploaded_file.type}. Allowed: JPG, PNG, BMP, GIF"
72
 
73
+ # Read file bytes with error handling
74
  try:
75
+ file_bytes = uploaded_file.read()
76
+ if len(file_bytes) == 0:
77
+ return None, "File appears to be empty"
78
+ except Exception as read_error:
79
+ return None, f"Error reading file: {str(read_error)}"
80
+
81
+ # Reset file pointer for PIL
82
+ uploaded_file.seek(0)
83
+
84
+ # Try to open and validate the image
85
+ try:
86
+ image = Image.open(BytesIO(file_bytes))
87
+
88
+ # Verify image is valid
89
+ image.verify()
90
+
91
+ # Reopen for actual use (verify() closes the image)
92
+ image = Image.open(BytesIO(file_bytes))
93
+
94
+ # Convert to RGB if necessary (handles RGBA, P mode, etc.)
95
+ if image.mode not in ('RGB', 'L'):
96
+ image = image.convert('RGB')
97
+
98
+ # Resize if too large (helps with memory in HF Spaces)
99
+ max_dimension = 1024
100
+ if max(image.size) > max_dimension:
101
+ image.thumbnail((max_dimension, max_dimension), Image.Resampling.LANCZOS)
102
+
103
+ return image, "Success"
104
+
105
  except Exception as img_error:
106
+ return None, f"Invalid or corrupted image: {str(img_error)}"
 
107
 
108
  except Exception as e:
109
+ return None, f"Unexpected error processing file: {str(e)}"
 
110
 
111
  # 1.6 TTS Generation Function with better error handling
112
  @st.cache_data
 
160
  "prompt_sent": False, "timer_running": {}, "last_tick": {},
161
  "project_title": "", "project_description": "", "upcycling_options": [],
162
  "plan_approved": False, "initial_plan": "", "user_image": None,
163
+ "upload_error": None, "upload_attempts": 0, "last_uploaded_file": None
164
  }
165
 
166
  # ─────────────────────────────────────────────────────────────────────────────
 
175
  "prompt_sent": False, "timer_running": {}, "last_tick": {},
176
  "project_title": "", "project_description": "", "upcycling_options": [],
177
  "plan_approved": False, "initial_plan": "", "user_image": None,
178
+ "upload_error": None, "upload_attempts": 0, "last_uploaded_file": None
179
  }
180
  st.success("βœ… Reset complete!")
181
  st.rerun()
 
379
  st.rerun()
380
 
381
  # ─────────────────────────────────────────────────────────────────────────────
382
+ # 4. APP LAYOUT - FIXED UPLOAD SECTION
383
  # ─────────────────────────────────────────────────────────────────────────────
384
 
385
  st.set_page_config(page_title="NeoFix DIY Assistant", page_icon="πŸ› οΈ", layout="wide")
 
399
  col1, col2 = st.columns([3, 1])
400
 
401
  with col1:
 
402
  st.markdown("### πŸ“· Upload Project Image")
403
 
404
+ # Show upload status
405
+ if st.session_state.app_state.get('upload_error'):
406
+ st.error(f"Upload Error: {st.session_state.app_state['upload_error']}")
407
+
408
  if st.session_state.app_state.get('upload_attempts', 0) > 0:
409
  st.info(f"Upload attempts: {st.session_state.app_state['upload_attempts']}")
410
 
411
+ # IMPROVED File uploader with unique key to force refresh
412
+ upload_key = f"file_upload_{st.session_state.app_state.get('upload_attempts', 0)}"
413
  uploaded_image = st.file_uploader(
414
+ "Choose an image file",
415
  type=["jpg", "jpeg", "png", "bmp", "gif"],
416
  accept_multiple_files=False,
417
+ key=upload_key,
418
+ help="Supported: JPG, PNG, BMP, GIF (max 5MB)"
419
  )
420
 
421
+ # Process uploaded image immediately
422
+ processed_image = None
423
+ upload_status = ""
 
 
 
424
 
425
+ if uploaded_image is not None:
426
+ # Check if this is a new file upload
427
+ current_file_id = f"{uploaded_image.name}_{uploaded_image.size}"
428
+ if current_file_id != st.session_state.app_state.get('last_uploaded_file'):
429
+ st.session_state.app_state['last_uploaded_file'] = current_file_id
430
+
431
+ with st.spinner("Processing uploaded image..."):
432
+ processed_image, upload_status = handle_uploaded_file(uploaded_image)
433
+
434
+ if processed_image is not None:
435
+ st.session_state.app_state['upload_error'] = None
436
+ st.success("βœ… Image uploaded and processed successfully!")
437
+ st.image(processed_image, caption="Uploaded image preview", use_container_width=True)
438
+ else:
439
+ st.session_state.app_state['upload_error'] = upload_status
440
+ st.session_state.app_state['upload_attempts'] += 1
441
+ st.error(f"❌ {upload_status}")
442
+ else:
443
+ # File already processed, show cached result
444
+ if st.session_state.app_state.get('upload_error') is None:
445
+ processed_image, _ = handle_uploaded_file(uploaded_image)
446
+ if processed_image:
447
+ st.success("βœ… Image ready for analysis!")
448
+ st.image(processed_image, caption="Uploaded image preview", use_container_width=True)
449
+
450
+ # Alternative camera input
451
+ st.markdown("##### Alternative: Take a photo")
452
+ camera_image = st.camera_input("Take a picture", key=f"camera_{st.session_state.app_state.get('upload_attempts', 0)}")
453
+ if camera_image and not uploaded_image:
454
+ with st.spinner("Processing camera image..."):
455
+ processed_image, upload_status = handle_uploaded_file(camera_image)
456
+ if processed_image is not None:
457
+ st.session_state.app_state['upload_error'] = None
458
+ st.success("βœ… Photo captured and processed!")
459
+ st.image(processed_image, caption="Camera photo preview", use_container_width=True)
460
  else:
461
+ st.error(f"❌ {upload_status}")
 
462
 
463
  context_text = st.text_area(
464
  "✏️ Describe the issue or your goal (optional but recommended)",
 
469
  with col2:
470
  st.markdown("### Actions")
471
 
472
+ # Get AI Guidance button - only enabled when image is ready
473
+ has_valid_image = (uploaded_image is not None or camera_image is not None) and st.session_state.app_state.get('upload_error') is None
474
+
475
+ if st.button(
476
+ "πŸš€ Get AI Guidance",
477
+ type="primary",
478
+ use_container_width=True,
479
+ disabled=not has_valid_image
480
+ ):
481
+ image_to_analyze = None
482
+
483
+ # Determine which image to use
484
  if uploaded_image:
485
+ image_to_analyze, status = handle_uploaded_file(uploaded_image)
486
+ elif camera_image:
487
+ image_to_analyze, status = handle_uploaded_file(camera_image)
488
+
489
+ if image_to_analyze is not None:
490
+ initial_analysis(image_to_analyze, context_text)
491
+ st.rerun()
492
  else:
493
+ st.error(f"❌ Image processing failed: {status}")
494
+
495
+ # Status message for button
496
+ if not has_valid_image:
497
+ if uploaded_image is None and camera_image is None:
498
  st.warning("⚠️ Please upload an image first!")
499
+ elif st.session_state.app_state.get('upload_error'):
500
+ st.warning("⚠️ Fix upload error first!")
501
 
502
  # Troubleshooting section
503
  with st.expander("πŸ”§ Upload Troubleshooting"):
504
  st.markdown("""
505
+ **Common fixes:**
506
+ 1. **Refresh upload**: Click button below
507
+ 2. **Check file size**: Max 5MB
508
+ 3. **Try different format**: JPG works best
509
+ 4. **Use camera**: If file upload fails
510
+ 5. **Clear browser cache**: Ctrl+Shift+Delete
511
  """)
512
 
513
+ if st.button("πŸ”„ Reset Upload", use_container_width=True):
514
  st.session_state.app_state['upload_attempts'] = 0
515
  st.session_state.app_state['upload_error'] = None
516
+ st.session_state.app_state['last_uploaded_file'] = None
517
  st.rerun()
518
+
519
+ # Debug info
520
+ if st.checkbox("Show debug info"):
521
+ st.json({
522
+ "upload_attempts": st.session_state.app_state.get('upload_attempts', 0),
523
+ "upload_error": st.session_state.app_state.get('upload_error'),
524
+ "last_file": st.session_state.app_state.get('last_uploaded_file'),
525
+ "has_uploaded_file": uploaded_image is not None,
526
+ "has_camera_image": camera_image is not None
527
+ })
528
 
529
  if st.button("πŸ”„ Start Over", use_container_width=True):
530
  reset_state()