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

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +159 -178
main.py CHANGED
@@ -758,67 +758,96 @@ def create_project():
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
 
823
  TOOLS AND MATERIALS:
824
  - Tool A
@@ -828,146 +857,98 @@ def approve_project_plan(project_id):
828
  1. First step instructions.
829
  2. Second step instructions...
830
  """
831
- context_time = time.time() - context_start
832
- logger.info(f"[PROJECT APPROVAL] Context preparation completed in {context_time:.3f}s")
833
-
834
- try:
835
- # AI generation timing
836
- ai_start = time.time()
837
- logger.info(f"[PROJECT APPROVAL] Starting AI generation with model: {GENERATION_MODEL}")
838
-
839
- chat = client.chats.create(
840
- model=GENERATION_MODEL,
841
- config=types.GenerateContentConfig(response_modalities=["Text", "Image"])
842
- )
843
- full_resp = chat.send_message([detailed_prompt, pil_image])
844
- ai_time = time.time() - ai_start
845
- logger.info(f"[PROJECT APPROVAL] AI generation completed in {ai_time:.3f}s")
846
-
847
- # Response parsing timing
848
- parsing_start = time.time()
849
- gen_parts = full_resp.candidates[0].content.parts
850
-
851
- combined_text = ""
852
- inline_images = []
853
- for part in gen_parts:
854
- if part.text is not None:
855
- combined_text += part.text + "\n"
856
- if part.inline_data is not None:
857
- img = Image.open(io.BytesIO(part.inline_data.data)).convert('RGB')
858
- inline_images.append(img)
859
-
860
- combined_text = combined_text.strip()
861
- parsing_time = time.time() - parsing_start
862
- logger.info(f"[PROJECT APPROVAL] Response parsing completed in {parsing_time:.3f}s, found {len(inline_images)} images")
863
-
864
- # Text extraction timing
865
- extraction_start = time.time()
866
- tools_section = re.search(r"TOOLS AND MATERIALS:\s*(.*?)\s*STEPS:", combined_text, re.DOTALL).group(1).strip()
867
- steps_section = re.search(r"STEPS:\s*(.*)", combined_text, re.DOTALL).group(1).strip()
868
-
869
- tools_list = [line.strip("- ").strip() for line in tools_section.split('\n') if line.strip()]
870
- parsed_steps = parse_numbered_steps(steps_section)
871
- extraction_time = time.time() - extraction_start
872
- logger.info(f"[PROJECT APPROVAL] Text extraction completed in {extraction_time:.3f}s, tools: {len(tools_list)}, steps: {len(parsed_steps)}")
873
-
874
- if len(parsed_steps) != len(inline_images):
875
- logger.error(f"[PROJECT APPROVAL] ERROR: AI response mismatch - Steps: {len(parsed_steps)}, Images: {len(inline_images)}")
876
- return jsonify({'error': 'AI response mismatch: Steps and images do not match.'}), 500
877
-
878
- # Step processing timing
879
- step_processing_start = time.time()
880
- final_steps = []
881
- total_upload_time = 0
882
- total_tts_time = 0
883
-
884
- for i, step_info in enumerate(parsed_steps):
885
- logger.info(f"[PROJECT APPROVAL] Processing step {i+1}/{len(parsed_steps)}")
886
-
887
- # Image upload timing
888
- image_upload_start = time.time()
889
- img_byte_arr = io.BytesIO()
890
- inline_images[i].save(img_byte_arr, format='JPEG', optimize=True, quality=70)
891
- img_path = f"users/{uid}/projects/{project_id}/steps/step_{i+1}_image.jpg"
892
- img_url = upload_to_storage(img_byte_arr.getvalue(), img_path, 'image/jpeg')
893
- image_upload_time = time.time() - image_upload_start
894
- total_upload_time += image_upload_time
895
- logger.info(f"[PROJECT APPROVAL] Step {i+1} image upload completed in {image_upload_time:.3f}s")
896
-
897
- # TTS generation timing
898
- tts_start = time.time()
899
- narration_url = generate_tts_audio_and_upload(step_info['text'], uid, project_id, i + 1)
900
- tts_time = time.time() - tts_start
901
- total_tts_time += tts_time
902
- logger.info(f"[PROJECT APPROVAL] Step {i+1} TTS generation completed in {tts_time:.3f}s")
903
-
904
- step_info.update({
905
- "imageUrl": img_url,
906
- "narrationUrl": narration_url,
907
- "isDone": False,
908
- "notes": ""
909
- })
910
- final_steps.append(step_info)
911
-
912
- step_processing_time = time.time() - step_processing_start
913
- logger.info(f"[PROJECT APPROVAL] All steps processing completed in {step_processing_time:.3f}s")
914
- logger.info(f"[PROJECT APPROVAL] Total upload time: {total_upload_time:.3f}s, Total TTS time: {total_tts_time:.3f}s")
915
-
916
- # Database update timing
917
- db_update_start = time.time()
918
- update_data = {
919
- "status": "ready",
920
- "toolsList": tools_list,
921
- "steps": final_steps,
922
- "selectedOption": selected_option or ""
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():
 
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
 
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():