rairo commited on
Commit
f5caf13
·
verified ·
1 Parent(s): f73fa83

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +180 -161
main.py CHANGED
@@ -758,197 +758,216 @@ def create_project():
758
  return jsonify({'error': f"An error occurred: {e}"}), 500
759
 
760
 
761
- # --------------------------------------------------------
762
  @app.route('/api/projects/<string:project_id>/approve', methods=['PUT'])
763
  def approve_project_plan(project_id):
764
  start_time = time.time()
765
- logger.info(f"[PROJECT APPROVAL] ▶️ Begin process project={project_id}")
766
-
767
- # ───────────────────────────────────────────────────────────
768
- # 1. AUTH
769
- # ───────────────────────────────────────────────────────────
770
- auth_t0 = time.time()
771
  uid = verify_token(request.headers.get('Authorization'))
772
  if not uid:
773
- logger.error(f"[PROJECT APPROVAL] Unauthorized project={project_id}")
774
  return jsonify({'error': 'Unauthorized'}), 401
775
- auth_time = time.time() - auth_t0
776
- logger.info(f"[TIMING] auth={auth_time:.3f}s uid={uid}")
777
 
778
- # ───────────────────────────────────────────────────────────
779
- # 2. USER / CREDIT CHECK
780
- # ───────────────────────────────────────────────────────────
781
- uc_t0 = time.time()
782
  user_ref = db_ref.child(f'users/{uid}')
783
  user_data = user_ref.get()
784
- credits = user_data.get('credits', 0) if user_data else 0
785
- if credits < 5:
786
- logger.error(f"[PROJECT APPROVAL] ❌ Insufficient credits • uid={uid} credits={credits}")
787
  return jsonify({'error': 'Insufficient credits'}), 402
788
- uc_time = time.time() - uc_t0
789
- logger.info(f"[TIMING] user_fetch={uc_time:.3f}s credits={credits}")
790
 
791
- # ───────────────────────────────────────────────────────────
792
- # 3. PROJECT FETCH
793
- # ───────────────────────────────────────────────────────────
794
- pf_t0 = time.time()
795
  project_ref = db_ref.child(f'projects/{project_id}')
796
  project_data = project_ref.get()
797
  if not project_data or project_data.get('uid') != uid:
798
- logger.error(f"[PROJECT APPROVAL] ❌ Not found / access denied project={project_id} uid={uid}")
799
  return jsonify({'error': 'Project not found or access denied'}), 404
800
- pf_time = time.time() - pf_t0
801
- logger.info(f"[TIMING] project_fetch={pf_time:.3f}s title='{project_data.get('projectTitle','?')}'")
802
-
803
- # ───────────────────────────────────────────────────────────
804
- # 4. DOUBLE-APPROVAL GUARD
805
- # ───────────────────────────────────────────────────────────
806
- if project_data.get('status') == 'ready':
807
- logger.warning(f"[PROJECT APPROVAL] ⏹ Already approved • project={project_id}")
808
- return jsonify({'error': 'Project is already approved'}), 409
809
-
810
- # ───────────────────────────────────────────────────────────
811
- # 5. VALIDATE selectedOption
812
- # ───────────────────────────────────────────────────────────
813
- val_t0 = time.time()
814
  selected_option = request.json.get('selectedOption')
815
- upcycling_options = project_data.get('upcyclingOptions', [])
816
-
817
- if upcycling_options: # choice required
818
- if not selected_option:
819
- logger.error(f"[PROJECT APPROVAL] ❌ Option required but missing • project={project_id}")
820
- return jsonify({'error': 'You must choose an option before approving.'}), 400
821
- if selected_option not in upcycling_options:
822
- logger.error(f"[PROJECT APPROVAL] ❌ Invalid option • chosen='{selected_option}'")
823
- return jsonify({'error': 'Invalid option selected.'}), 400
824
- else: # no options at all
825
- selected_option = None
826
- val_time = time.time() - val_t0
827
- logger.info(f"[TIMING] validation={val_time:.3f}s • selected='{selected_option}' options={len(upcycling_options)}")
828
-
829
- # ───────────────────────────────────────────────────────────
830
- # 6. IMAGE DOWNLOAD / PROCESSING
831
- # ───────────────────────────────────────────────────────────
832
- img_dl_t0 = time.time()
833
- resp = requests.get(project_data['userImageURL'])
834
- pil_image = Image.open(io.BytesIO(resp.content)).convert('RGB')
835
- img_dl_time = time.time() - img_dl_t0
836
- logger.info(f"[TIMING] image_download+proc={img_dl_time:.3f}s • bytes={len(resp.content)}")
837
-
838
- # ───────────────────────────────────────────────────────────
839
- # 7. PROMPT + AI GENERATION
840
- # ───────────────────────────────────────────────────────────
841
- prompt_t0 = time.time()
842
- context_line = (
843
  f"The user chose the upcycling project: '{selected_option}'."
844
  if selected_option
845
  else f"The user has approved the plan for '{project_data['projectTitle']}'."
846
  )
 
847
  detailed_prompt = f"""
848
  You are a DIY expert. The user wants to proceed with the project titled "{project_data['projectTitle']}".
849
- {context_line}
850
  Provide a detailed guide. For each step, you MUST provide a simple, clear illustrative image.
851
-
852
  TOOLS AND MATERIALS:
853
  - Tool A
854
  - Material B
855
-
856
  STEPS(Maximum 5 steps):
857
  1. First step instructions.
858
  2. Second step instructions...
859
  """
860
- chat = client.chats.create(
861
- model=GENERATION_MODEL,
862
- config=types.GenerateContentConfig(response_modalities=["Text", "Image"])
863
- )
864
- full_resp = chat.send_message([detailed_prompt, pil_image])
865
- prompt_time = time.time() - prompt_t0
866
- logger.info(f"[TIMING] ai_generation={prompt_time:.3f}s")
867
-
868
- # ───────────────────────────────────────────────────────────
869
- # 8. PARSE AI RESPONSE
870
- # ───────────────────────────────────────────────────────────
871
- parse_t0 = time.time()
872
- gen_parts = full_resp.candidates[0].content.parts
873
- combined_text, inline_images = "", []
874
- for part in gen_parts:
875
- if part.text:
876
- combined_text += part.text + "\n"
877
- if part.inline_data:
878
- inline_images.append(Image.open(io.BytesIO(part.inline_data.data)).convert('RGB'))
879
- tools_section = re.search(r"TOOLS AND MATERIALS:\s*(.*?)\s*STEPS:", combined_text, re.DOTALL).group(1).strip()
880
- steps_section = re.search(r"STEPS:\s*(.*)", combined_text, re.DOTALL).group(1).strip()
881
- tools_list = [ln.strip("- ").strip() for ln in tools_section.split('\n') if ln.strip()]
882
- parsed_steps = parse_numbered_steps(steps_section)
883
- if len(parsed_steps) != len(inline_images):
884
- logger.error("[PROJECT APPROVAL] ❌ Steps/images mismatch")
885
- return jsonify({'error': 'AI response mismatch: Steps and images do not match.'}), 500
886
- parse_time = time.time() - parse_t0
887
- logger.info(f"[TIMING] parse_response={parse_time:.3f}s • steps={len(parsed_steps)} imgs={len(inline_images)}")
888
-
889
- # ───────────────────────────────────────────────────────────
890
- # 9. STEP PROCESSING (uploads + TTS)
891
- # ───────────────────────────────────────────────────────────
892
- steps_t0 = time.time()
893
- total_upload, total_tts, final_steps = 0.0, 0.0, []
894
- for idx, step_info in enumerate(parsed_steps, 1):
895
- # image upload
896
- up_t0 = time.time()
897
- buf = io.BytesIO()
898
- inline_images[idx-1].save(buf, format='JPEG', optimize=True, quality=70)
899
- img_path = f"users/{uid}/projects/{project_id}/steps/step_{idx}_image.jpg"
900
- img_url = upload_to_storage(buf.getvalue(), img_path, 'image/jpeg')
901
- total_upload += time.time() - up_t0
902
-
903
- # TTS
904
- tts_t0 = time.time()
905
- narr_url = generate_tts_audio_and_upload(step_info['text'], uid, project_id, idx)
906
- total_tts += time.time() - tts_t0
907
-
908
- step_info.update({
909
- "imageUrl": img_url,
910
- "narrationUrl": narr_url,
911
- "isDone": False,
912
- "notes": ""
913
- })
914
- final_steps.append(step_info)
915
- steps_time = time.time() - steps_t0
916
- logger.info(f"[TIMING] steps_process={steps_time:.3f}s • uploads={total_upload:.3f}s tts={total_tts:.3f}s")
917
-
918
- # ───────────────────────────────────────────────────────────
919
- # 10. DATABASE UPDATE (clear menu!)
920
- # ───────────────────────────────────────────────────────────
921
- db_t0 = time.time()
922
- project_ref.update({
923
- "status": "ready",
924
- "selectedOption": selected_option or "",
925
- "upcyclingOptions": [], # => gone forever
926
- "toolsList": tools_list,
927
- "steps": final_steps
928
- })
929
- db_time = time.time() - db_t0
930
- logger.info(f"[TIMING] db_update={db_time:.3f}s")
931
-
932
- # credits deduction
933
- credits_t0 = time.time()
934
- user_ref.update({'credits': credits - 5})
935
- credits_time = time.time() - credits_t0
936
- logger.info(f"[TIMING] credits_update={credits_time:.3f}s • new_credits={credits-5}")
937
-
938
- # ───────────────────────────────────────────────────────────
939
- # 11. FINAL FETCH & RETURN
940
- # ───────────────────────────────────────────────────────────
941
- final_fetch_t0 = time.time()
942
- updated_project = project_ref.get()
943
- updated_project["projectId"] = project_id
944
- fetch_time = time.time() - final_fetch_t0
945
-
946
- total_time = time.time() - start_time
947
- logger.info(
948
- f"[PROJECT APPROVAL] ✅ SUCCESS • total={total_time:.3f}s "
949
- f"(fetch_final={fetch_time:.3f}s)"
950
- )
951
- return jsonify(updated_project), 200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
952
 
953
  @app.route('/api/projects', methods=['GET'])
954
  def list_projects():
 
758
  return jsonify({'error': f"An error occurred: {e}"}), 500
759
 
760
 
 
761
  @app.route('/api/projects/<string:project_id>/approve', methods=['PUT'])
762
  def approve_project_plan(project_id):
763
  start_time = time.time()
764
+ logger.info(f"[PROJECT APPROVAL] Starting approval process for project: {project_id}")
765
+
766
+ # Authorization timing
767
+ auth_start = time.time()
 
 
768
  uid = verify_token(request.headers.get('Authorization'))
769
  if not uid:
770
+ logger.error(f"[PROJECT APPROVAL] ERROR: Unauthorized access attempt for project: {project_id}")
771
  return jsonify({'error': 'Unauthorized'}), 401
772
+ auth_time = time.time() - auth_start
773
+ logger.info(f"[PROJECT APPROVAL] Authorization completed in {auth_time:.3f}s for user: {uid}")
774
 
775
+ # User data fetch timing
776
+ user_fetch_start = time.time()
 
 
777
  user_ref = db_ref.child(f'users/{uid}')
778
  user_data = user_ref.get()
779
+ if not user_data or user_data.get('credits', 0) < 5:
780
+ logger.error(f"[PROJECT APPROVAL] ERROR: Insufficient credits for user: {uid}, credits: {user_data.get('credits', 0) if user_data else 0}")
 
781
  return jsonify({'error': 'Insufficient credits'}), 402
782
+ user_fetch_time = time.time() - user_fetch_start
783
+ logger.info(f"[PROJECT APPROVAL] User data fetch completed in {user_fetch_time:.3f}s, credits: {user_data.get('credits', 0)}")
784
 
785
+ # Project data fetch timing
786
+ project_fetch_start = time.time()
 
 
787
  project_ref = db_ref.child(f'projects/{project_id}')
788
  project_data = project_ref.get()
789
  if not project_data or project_data.get('uid') != uid:
790
+ logger.error(f"[PROJECT APPROVAL] ERROR: Project not found or access denied - project_id: {project_id}, uid: {uid}")
791
  return jsonify({'error': 'Project not found or access denied'}), 404
792
+ project_fetch_time = time.time() - project_fetch_start
793
+ logger.info(f"[PROJECT APPROVAL] Project data fetch completed in {project_fetch_time:.3f}s for project: {project_data.get('projectTitle', 'Unknown')}")
794
+
795
+ # Image download and processing timing
 
 
 
 
 
 
 
 
 
 
796
  selected_option = request.json.get('selectedOption')
797
+ logger.info(f"[PROJECT APPROVAL] Selected option: {selected_option}")
798
+
799
+ image_download_start = time.time()
800
+ response = requests.get(project_data['userImageURL'])
801
+ image_download_time = time.time() - image_download_start
802
+ logger.info(f"[PROJECT APPROVAL] Image download completed in {image_download_time:.3f}s, size: {len(response.content)} bytes")
803
+
804
+ image_processing_start = time.time()
805
+ pil_image = Image.open(io.BytesIO(response.content)).convert('RGB')
806
+ image_processing_time = time.time() - image_processing_start
807
+ logger.info(f"[PROJECT APPROVAL] Image processing completed in {image_processing_time:.3f}s")
808
+
809
+ # Context preparation timing
810
+ context_start = time.time()
811
+ context = (
 
 
 
 
 
 
 
 
 
 
 
 
 
812
  f"The user chose the upcycling project: '{selected_option}'."
813
  if selected_option
814
  else f"The user has approved the plan for '{project_data['projectTitle']}'."
815
  )
816
+
817
  detailed_prompt = f"""
818
  You are a DIY expert. The user wants to proceed with the project titled "{project_data['projectTitle']}".
819
+ {context}
820
  Provide a detailed guide. For each step, you MUST provide a simple, clear illustrative image.
821
+ Format your response EXACTLY like this:
822
  TOOLS AND MATERIALS:
823
  - Tool A
824
  - Material B
 
825
  STEPS(Maximum 5 steps):
826
  1. First step instructions.
827
  2. Second step instructions...
828
  """
829
+ context_time = time.time() - context_start
830
+ logger.info(f"[PROJECT APPROVAL] Context preparation completed in {context_time:.3f}s")
831
+
832
+ try:
833
+ # AI generation timing
834
+ ai_start = time.time()
835
+ logger.info(f"[PROJECT APPROVAL] Starting AI generation with model: {GENERATION_MODEL}")
836
+
837
+ chat = client.chats.create(
838
+ model=GENERATION_MODEL,
839
+ config=types.GenerateContentConfig(response_modalities=["Text", "Image"])
840
+ )
841
+ full_resp = chat.send_message([detailed_prompt, pil_image])
842
+ ai_time = time.time() - ai_start
843
+ logger.info(f"[PROJECT APPROVAL] AI generation completed in {ai_time:.3f}s")
844
+
845
+ # Response parsing timing
846
+ parsing_start = time.time()
847
+ gen_parts = full_resp.candidates[0].content.parts
848
+
849
+ combined_text = ""
850
+ inline_images = []
851
+ for part in gen_parts:
852
+ if part.text is not None:
853
+ combined_text += part.text + "\n"
854
+ if part.inline_data is not None:
855
+ img = Image.open(io.BytesIO(part.inline_data.data)).convert('RGB')
856
+ inline_images.append(img)
857
+
858
+ combined_text = combined_text.strip()
859
+ parsing_time = time.time() - parsing_start
860
+ logger.info(f"[PROJECT APPROVAL] Response parsing completed in {parsing_time:.3f}s, found {len(inline_images)} images")
861
+
862
+ # Text extraction timing
863
+ extraction_start = time.time()
864
+ tools_section = re.search(r"TOOLS AND MATERIALS:\s*(.*?)\s*STEPS:", combined_text, re.DOTALL).group(1).strip()
865
+ steps_section = re.search(r"STEPS:\s*(.*)", combined_text, re.DOTALL).group(1).strip()
866
+
867
+ tools_list = [line.strip("- ").strip() for line in tools_section.split('\n') if line.strip()]
868
+ parsed_steps = parse_numbered_steps(steps_section)
869
+ extraction_time = time.time() - extraction_start
870
+ logger.info(f"[PROJECT APPROVAL] Text extraction completed in {extraction_time:.3f}s, tools: {len(tools_list)}, steps: {len(parsed_steps)}")
871
+
872
+ if len(parsed_steps) != len(inline_images):
873
+ logger.error(f"[PROJECT APPROVAL] ERROR: AI response mismatch - Steps: {len(parsed_steps)}, Images: {len(inline_images)}")
874
+ return jsonify({'error': 'AI response mismatch: Steps and images do not match.'}), 500
875
+
876
+ # Step processing timing
877
+ step_processing_start = time.time()
878
+ final_steps = []
879
+ total_upload_time = 0
880
+ total_tts_time = 0
881
+
882
+ for i, step_info in enumerate(parsed_steps):
883
+ logger.info(f"[PROJECT APPROVAL] Processing step {i+1}/{len(parsed_steps)}")
884
+
885
+ # Image upload timing
886
+ image_upload_start = time.time()
887
+ img_byte_arr = io.BytesIO()
888
+ inline_images[i].save(img_byte_arr, format='JPEG', optimize=True, quality=70)
889
+ img_path = f"users/{uid}/projects/{project_id}/steps/step_{i+1}_image.jpg"
890
+ img_url = upload_to_storage(img_byte_arr.getvalue(), img_path, 'image/jpeg')
891
+ image_upload_time = time.time() - image_upload_start
892
+ total_upload_time += image_upload_time
893
+ logger.info(f"[PROJECT APPROVAL] Step {i+1} image upload completed in {image_upload_time:.3f}s")
894
+
895
+ # TTS generation timing
896
+ tts_start = time.time()
897
+ narration_url = generate_tts_audio_and_upload(step_info['text'], uid, project_id, i + 1)
898
+ tts_time = time.time() - tts_start
899
+ total_tts_time += tts_time
900
+ logger.info(f"[PROJECT APPROVAL] Step {i+1} TTS generation completed in {tts_time:.3f}s")
901
+
902
+ step_info.update({
903
+ "imageUrl": img_url,
904
+ "narrationUrl": narration_url,
905
+ "isDone": False,
906
+ "notes": ""
907
+ })
908
+ final_steps.append(step_info)
909
+
910
+ step_processing_time = time.time() - step_processing_start
911
+ logger.info(f"[PROJECT APPROVAL] All steps processing completed in {step_processing_time:.3f}s")
912
+ logger.info(f"[PROJECT APPROVAL] Total upload time: {total_upload_time:.3f}s, Total TTS time: {total_tts_time:.3f}s")
913
+
914
+ # Database update timing
915
+ db_update_start = time.time()
916
+ update_data = {
917
+ "status": "ready",
918
+ "toolsList": tools_list,
919
+ "steps": final_steps,
920
+ "selectedOption": selected_option or "",
921
+ #update upcycling options to ensure only selected option persists
922
+ "upcyclingOptions": [selected_option] if selected_option else []
923
+ }
924
+ project_ref.update(update_data)
925
+ db_update_time = time.time() - db_update_start
926
+ logger.info(f"[PROJECT APPROVAL] Database update completed in {db_update_time:.3f}s")
927
+
928
+ # Final project fetch timing
929
+ final_fetch_start = time.time()
930
+ updated_project = project_ref.get()
931
+ updated_project["projectId"] = project_id
932
+ final_fetch_time = time.time() - final_fetch_start
933
+ logger.info(f"[PROJECT APPROVAL] Final project fetch completed in {final_fetch_time:.3f}s")
934
+
935
+ # Credits deduction timing
936
+ credits_update_start = time.time()
937
+ user_ref.update({'credits': user_data.get('credits', 0) - 5})
938
+ credits_update_time = time.time() - credits_update_start
939
+ logger.info(f"[PROJECT APPROVAL] Credits update completed in {credits_update_time:.3f}s")
940
+
941
+ # Total time calculation
942
+ total_time = time.time() - start_time
943
+ logger.info(f"[PROJECT APPROVAL] SUCCESS: Project approval completed in {total_time:.3f}s")
944
+ logger.info(f"[PROJECT APPROVAL] TIMING BREAKDOWN:")
945
+ logger.info(f"[PROJECT APPROVAL] - Authorization: {auth_time:.3f}s")
946
+ logger.info(f"[PROJECT APPROVAL] - User fetch: {user_fetch_time:.3f}s")
947
+ logger.info(f"[PROJECT APPROVAL] - Project fetch: {project_fetch_time:.3f}s")
948
+ logger.info(f"[PROJECT APPROVAL] - Image download: {image_download_time:.3f}s")
949
+ logger.info(f"[PROJECT APPROVAL] - Image processing: {image_processing_time:.3f}s")
950
+ logger.info(f"[PROJECT APPROVAL] - Context prep: {context_time:.3f}s")
951
+ logger.info(f"[PROJECT APPROVAL] - AI generation: {ai_time:.3f}s")
952
+ logger.info(f"[PROJECT APPROVAL] - Response parsing: {parsing_time:.3f}s")
953
+ logger.info(f"[PROJECT APPROVAL] - Text extraction: {extraction_time:.3f}s")
954
+ logger.info(f"[PROJECT APPROVAL] - Step processing: {step_processing_time:.3f}s")
955
+ logger.info(f"[PROJECT APPROVAL] - Total uploads: {total_upload_time:.3f}s")
956
+ logger.info(f"[PROJECT APPROVAL] - Total TTS: {total_tts_time:.3f}s")
957
+ logger.info(f"[PROJECT APPROVAL] - DB update: {db_update_time:.3f}s")
958
+ logger.info(f"[PROJECT APPROVAL] - Final fetch: {final_fetch_time:.3f}s")
959
+ logger.info(f"[PROJECT APPROVAL] - Credits update: {credits_update_time:.3f}s")
960
+
961
+ return jsonify(updated_project)
962
+
963
+ except Exception as e:
964
+ total_time = time.time() - start_time
965
+ logger.error(f"[PROJECT APPROVAL] ERROR: Exception occurred after {total_time:.3f}s: {e}")
966
+ logger.error(f"[PROJECT APPROVAL] Error type: {type(e).__name__}")
967
+ logger.error(f"[PROJECT APPROVAL] Project ID: {project_id}, User ID: {uid}")
968
+ import traceback
969
+ logger.error(f"[PROJECT APPROVAL] Full traceback: {traceback.format_exc()}")
970
+ return jsonify({'error': str(e)}), 500
971
 
972
  @app.route('/api/projects', methods=['GET'])
973
  def list_projects():