rairo commited on
Commit
367e4d8
·
verified ·
1 Parent(s): db3b812

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +110 -39
main.py CHANGED
@@ -575,70 +575,141 @@ def approve_project_plan(project_id):
575
  return jsonify({'error': 'Project not found or access denied'}), 404
576
 
577
  selected_option = request.json.get('selectedOption')
578
- response = requests.get(project_data['userImageURL'])
579
- pil_image = Image.open(io.BytesIO(response.content))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
580
 
581
  context = f"The user chose the upcycling project: '{selected_option}'." if selected_option else f"The user has approved the plan for '{project_data['projectTitle']}'."
582
 
583
- detailed_prompt = f"""
584
- You are a DIY expert. The user wants to proceed with the project titled "{project_data['projectTitle']}".
585
- {context}
586
- Provide a detailed guide. For each step, you MUST provide a simple, clear illustrative image.
587
- Format your response EXACTLY like this:
 
 
 
 
 
 
588
 
589
- TOOLS AND MATERIALS:
590
- - Tool A
591
- - Material B
592
-
593
- STEPS(Maximum 7 steps):
594
- 1. First step instructions.
595
- 2. Second step instructions...
596
- """
597
  try:
598
- chat = client.chats.create(model=GENERATION_MODEL, config=types.GenerateContentConfig(response_modalities=["Text", "Image"]))
599
- full_resp = chat.send_message([detailed_prompt, pil_image])
 
600
 
601
- # Fix: Access the parts through the correct structure
 
 
 
 
 
 
 
 
 
 
602
  gen_parts = full_resp.candidates[0].content.parts
603
 
604
  combined_text = ""
605
  inline_images = []
 
 
606
  for part in gen_parts:
607
- if part.text is not None:
608
  combined_text += part.text + "\n"
609
- if part.inline_data is not None:
610
- img = Image.open(BytesIO(part.inline_data.data))
611
- inline_images.append(img)
 
 
 
 
612
 
613
  combined_text = combined_text.strip()
614
-
615
- tools_section = re.search(r"TOOLS AND MATERIALS:\s*(.*?)\s*STEPS:", combined_text, re.DOTALL).group(1).strip()
616
- steps_section = re.search(r"STEPS:\s*(.*)", combined_text, re.DOTALL).group(1).strip()
617
 
618
- tools_list = [line.strip("- ").strip() for line in tools_section.split('\n') if line.strip()]
 
 
 
 
 
 
 
 
 
 
 
619
  parsed_steps = parse_numbered_steps(steps_section)
620
 
621
- if len(parsed_steps) != len(inline_images): return jsonify({'error': 'AI response mismatch: Steps and images do not match.'}), 500
 
622
 
 
623
  final_steps = []
624
- for i, step_info in enumerate(parsed_steps):
625
- img_byte_arr = io.BytesIO()
626
- inline_images[i].save(img_byte_arr, format='PNG')
627
- img_path = f"users/{uid}/projects/{project_id}/steps/step_{i+1}_image.png"
628
- img_url = upload_to_storage(img_byte_arr.getvalue(), img_path, 'image/png')
629
-
630
- narration_url = generate_tts_audio_and_upload(step_info['text'], uid, project_id, i + 1)
631
- step_info.update({"imageUrl": img_url, "narrationUrl": narration_url, "isDone": False, "notes": ""})
632
- final_steps.append(step_info)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
633
 
634
- update_data = {"status": "ready", "toolsList": tools_list, "steps": final_steps, "selectedOption": selected_option or ""}
635
  project_ref.update(update_data)
636
 
637
  return jsonify({"success": True, **update_data})
638
 
639
  except Exception as e:
640
- print(traceback.format_exc())
641
- return jsonify({'error': f"Failed to generate detailed guide: {e}"}), 500
642
 
643
  @app.route('/api/projects', methods=['GET'])
644
  def list_projects():
 
575
  return jsonify({'error': 'Project not found or access denied'}), 404
576
 
577
  selected_option = request.json.get('selectedOption')
578
+
579
+ # Optimized image download with timeout and session reuse
580
+ try:
581
+ session = requests.Session()
582
+ session.headers.update({'User-Agent': 'Mozilla/5.0'})
583
+
584
+ with session.get(project_data['userImageURL'], timeout=15, stream=True) as response:
585
+ response.raise_for_status()
586
+ image_data = io.BytesIO()
587
+ for chunk in response.iter_content(chunk_size=8192):
588
+ image_data.write(chunk)
589
+ image_data.seek(0)
590
+ pil_image = Image.open(image_data)
591
+
592
+ except Exception as e:
593
+ return jsonify({'error': f'Failed to download image: {str(e)}'}), 400
594
 
595
  context = f"The user chose the upcycling project: '{selected_option}'." if selected_option else f"The user has approved the plan for '{project_data['projectTitle']}'."
596
 
597
+ # Streamlined prompt
598
+ detailed_prompt = f"""You are a DIY expert. The user wants to proceed with the project titled "{project_data['projectTitle']}".
599
+ {context}
600
+ Provide a detailed guide. For each step, you MUST provide a simple, clear illustrative image.
601
+ Format your response EXACTLY like this:
602
+ TOOLS AND MATERIALS:
603
+ - Tool A
604
+ - Material B
605
+ STEPS(Maximum 7 steps):
606
+ 1. First step instructions.
607
+ 2. Second step instructions..."""
608
 
 
 
 
 
 
 
 
 
609
  try:
610
+ # AI generation with retry logic
611
+ max_retries = 2
612
+ full_resp = None
613
 
614
+ for attempt in range(max_retries):
615
+ try:
616
+ chat = client.chats.create(model=GENERATION_MODEL, config=types.GenerateContentConfig(response_modalities=["Text", "Image"]))
617
+ full_resp = chat.send_message([detailed_prompt, pil_image])
618
+ break
619
+ except Exception as e:
620
+ if attempt == max_retries - 1:
621
+ raise e
622
+ print(f"AI generation attempt {attempt + 1} failed: {e}")
623
+
624
+ # Optimized response parsing
625
  gen_parts = full_resp.candidates[0].content.parts
626
 
627
  combined_text = ""
628
  inline_images = []
629
+
630
+ # Single pass through parts
631
  for part in gen_parts:
632
+ if hasattr(part, 'text') and part.text:
633
  combined_text += part.text + "\n"
634
+ if hasattr(part, 'inline_data') and part.inline_data:
635
+ try:
636
+ img = Image.open(BytesIO(part.inline_data.data))
637
+ inline_images.append(img)
638
+ except Exception as e:
639
+ print(f"Failed to process image: {e}")
640
+ continue
641
 
642
  combined_text = combined_text.strip()
 
 
 
643
 
644
+ # More robust regex parsing
645
+ tools_match = re.search(r"TOOLS AND MATERIALS:\s*(.*?)\s*(?=STEPS:|$)", combined_text, re.DOTALL | re.IGNORECASE)
646
+ steps_match = re.search(r"STEPS[^:]*:\s*(.*)", combined_text, re.DOTALL | re.IGNORECASE)
647
+
648
+ if not tools_match or not steps_match:
649
+ return jsonify({'error': 'AI response format error - could not parse tools and steps'}), 500
650
+
651
+ tools_section = tools_match.group(1).strip()
652
+ steps_section = steps_match.group(1).strip()
653
+
654
+ # Efficient list comprehension
655
+ tools_list = [line.strip().lstrip('- ').strip() for line in tools_section.split('\n') if line.strip()]
656
  parsed_steps = parse_numbered_steps(steps_section)
657
 
658
+ if len(parsed_steps) != len(inline_images):
659
+ return jsonify({'error': f'AI response mismatch: {len(parsed_steps)} steps vs {len(inline_images)} images'}), 500
660
 
661
+ # Sequential processing with progress tracking
662
  final_steps = []
663
+ session = requests.Session() # Reuse session for uploads
664
+
665
+ for i, (step_info, image) in enumerate(zip(parsed_steps, inline_images)):
666
+ try:
667
+ # Optimize image before upload
668
+ if image.mode == 'RGBA':
669
+ image = image.convert('RGB')
670
+
671
+ # Compress image to reduce upload time
672
+ img_byte_arr = io.BytesIO()
673
+ image.save(img_byte_arr, format='JPEG', quality=85, optimize=True)
674
+ img_path = f"users/{uid}/projects/{project_id}/steps/step_{i+1}_image.jpg"
675
+
676
+ # Upload with timeout
677
+ img_url = upload_to_storage(img_byte_arr.getvalue(), img_path, 'image/jpeg')
678
+
679
+ # Generate audio with timeout handling
680
+ try:
681
+ narration_url = generate_tts_audio_and_upload(step_info['text'], uid, project_id, i + 1)
682
+ except Exception as audio_error:
683
+ print(f"Audio generation failed for step {i+1}: {audio_error}")
684
+ narration_url = "" # Continue without audio if it fails
685
+
686
+ step_info.update({
687
+ "imageUrl": img_url,
688
+ "narrationUrl": narration_url,
689
+ "isDone": False,
690
+ "notes": ""
691
+ })
692
+ final_steps.append(step_info)
693
+
694
+ except Exception as step_error:
695
+ print(f"Failed to process step {i+1}: {step_error}")
696
+ return jsonify({'error': f'Failed to process step {i+1}'}), 500
697
+
698
+ # Single database update
699
+ update_data = {
700
+ "status": "ready",
701
+ "toolsList": tools_list,
702
+ "steps": final_steps,
703
+ "selectedOption": selected_option or ""
704
+ }
705
 
 
706
  project_ref.update(update_data)
707
 
708
  return jsonify({"success": True, **update_data})
709
 
710
  except Exception as e:
711
+ print(f"Error in approve_project_plan: {traceback.format_exc()}")
712
+ return jsonify({'error': f"Failed to generate detailed guide: {str(e)}"}), 500
713
 
714
  @app.route('/api/projects', methods=['GET'])
715
  def list_projects():