Update main.py
Browse files
main.py
CHANGED
|
@@ -619,7 +619,7 @@ def create_project():
|
|
| 619 |
}
|
| 620 |
db_ref.child(f'projects/{project_id}').set(project_data)
|
| 621 |
|
| 622 |
-
user_ref.update({'credits': user_data.get('credits',
|
| 623 |
return jsonify(project_data), 201
|
| 624 |
|
| 625 |
except Exception as e:
|
|
@@ -629,19 +629,54 @@ def create_project():
|
|
| 629 |
|
| 630 |
@app.route('/api/projects/<string:project_id>/approve', methods=['PUT'])
|
| 631 |
def approve_project_plan(project_id):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 632 |
uid = verify_token(request.headers.get('Authorization'))
|
| 633 |
if not uid:
|
|
|
|
| 634 |
return jsonify({'error': 'Unauthorized'}), 401
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 635 |
|
|
|
|
|
|
|
| 636 |
project_ref = db_ref.child(f'projects/{project_id}')
|
| 637 |
project_data = project_ref.get()
|
| 638 |
if not project_data or project_data.get('uid') != uid:
|
|
|
|
| 639 |
return jsonify({'error': 'Project not found or access denied'}), 404
|
|
|
|
|
|
|
| 640 |
|
|
|
|
| 641 |
selected_option = request.json.get('selectedOption')
|
|
|
|
|
|
|
|
|
|
| 642 |
response = requests.get(project_data['userImageURL'])
|
|
|
|
|
|
|
|
|
|
|
|
|
| 643 |
pil_image = Image.open(io.BytesIO(response.content)).convert('RGB')
|
|
|
|
|
|
|
| 644 |
|
|
|
|
|
|
|
| 645 |
context = (
|
| 646 |
f"The user chose the upcycling project: '{selected_option}'."
|
| 647 |
if selected_option
|
|
@@ -662,13 +697,24 @@ def approve_project_plan(project_id):
|
|
| 662 |
1. First step instructions.
|
| 663 |
2. Second step instructions...
|
| 664 |
"""
|
|
|
|
|
|
|
| 665 |
|
| 666 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 667 |
chat = client.chats.create(
|
| 668 |
model=GENERATION_MODEL,
|
| 669 |
config=types.GenerateContentConfig(response_modalities=["Text", "Image"])
|
| 670 |
)
|
| 671 |
full_resp = chat.send_message([detailed_prompt, pil_image])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 672 |
gen_parts = full_resp.candidates[0].content.parts
|
| 673 |
|
| 674 |
combined_text = ""
|
|
@@ -681,24 +727,49 @@ def approve_project_plan(project_id):
|
|
| 681 |
inline_images.append(img)
|
| 682 |
|
| 683 |
combined_text = combined_text.strip()
|
|
|
|
|
|
|
| 684 |
|
|
|
|
|
|
|
| 685 |
tools_section = re.search(r"TOOLS AND MATERIALS:\s*(.*?)\s*STEPS:", combined_text, re.DOTALL).group(1).strip()
|
| 686 |
steps_section = re.search(r"STEPS:\s*(.*)", combined_text, re.DOTALL).group(1).strip()
|
| 687 |
|
| 688 |
tools_list = [line.strip("- ").strip() for line in tools_section.split('\n') if line.strip()]
|
| 689 |
parsed_steps = parse_numbered_steps(steps_section)
|
|
|
|
|
|
|
| 690 |
|
| 691 |
if len(parsed_steps) != len(inline_images):
|
|
|
|
| 692 |
return jsonify({'error': 'AI response mismatch: Steps and images do not match.'}), 500
|
| 693 |
|
|
|
|
|
|
|
| 694 |
final_steps = []
|
|
|
|
|
|
|
|
|
|
| 695 |
for i, step_info in enumerate(parsed_steps):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 696 |
img_byte_arr = io.BytesIO()
|
| 697 |
inline_images[i].save(img_byte_arr, format='JPEG', optimize=True, quality=70)
|
| 698 |
img_path = f"users/{uid}/projects/{project_id}/steps/step_{i+1}_image.jpg"
|
| 699 |
img_url = upload_to_storage(img_byte_arr.getvalue(), img_path, 'image/jpeg')
|
|
|
|
|
|
|
|
|
|
| 700 |
|
|
|
|
|
|
|
| 701 |
narration_url = generate_tts_audio_and_upload(step_info['text'], uid, project_id, i + 1)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 702 |
step_info.update({
|
| 703 |
"imageUrl": img_url,
|
| 704 |
"narrationUrl": narration_url,
|
|
@@ -707,6 +778,12 @@ def approve_project_plan(project_id):
|
|
| 707 |
})
|
| 708 |
final_steps.append(step_info)
|
| 709 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 710 |
update_data = {
|
| 711 |
"status": "ready",
|
| 712 |
"toolsList": tools_list,
|
|
@@ -714,14 +791,51 @@ def approve_project_plan(project_id):
|
|
| 714 |
"selectedOption": selected_option or ""
|
| 715 |
}
|
| 716 |
project_ref.update(update_data)
|
|
|
|
|
|
|
| 717 |
|
| 718 |
-
#
|
|
|
|
| 719 |
updated_project = project_ref.get()
|
| 720 |
updated_project["projectId"] = project_id
|
| 721 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 722 |
return jsonify(updated_project)
|
| 723 |
|
| 724 |
except Exception as e:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 725 |
return jsonify({'error': str(e)}), 500
|
| 726 |
|
| 727 |
@app.route('/api/projects', methods=['GET'])
|
|
|
|
| 619 |
}
|
| 620 |
db_ref.child(f'projects/{project_id}').set(project_data)
|
| 621 |
|
| 622 |
+
user_ref.update({'credits': user_data.get('credits', 0) - 1})
|
| 623 |
return jsonify(project_data), 201
|
| 624 |
|
| 625 |
except Exception as e:
|
|
|
|
| 629 |
|
| 630 |
@app.route('/api/projects/<string:project_id>/approve', methods=['PUT'])
|
| 631 |
def approve_project_plan(project_id):
|
| 632 |
+
start_time = time.time()
|
| 633 |
+
logger.info(f"[PROJECT APPROVAL] Starting approval process for project: {project_id}")
|
| 634 |
+
|
| 635 |
+
# Authorization timing
|
| 636 |
+
auth_start = time.time()
|
| 637 |
uid = verify_token(request.headers.get('Authorization'))
|
| 638 |
if not uid:
|
| 639 |
+
logger.error(f"[PROJECT APPROVAL] ERROR: Unauthorized access attempt for project: {project_id}")
|
| 640 |
return jsonify({'error': 'Unauthorized'}), 401
|
| 641 |
+
auth_time = time.time() - auth_start
|
| 642 |
+
logger.info(f"[PROJECT APPROVAL] Authorization completed in {auth_time:.3f}s for user: {uid}")
|
| 643 |
+
|
| 644 |
+
# User data fetch timing
|
| 645 |
+
user_fetch_start = time.time()
|
| 646 |
+
user_ref = db_ref.child(f'users/{uid}')
|
| 647 |
+
user_data = user_ref.get()
|
| 648 |
+
if not user_data or user_data.get('credits', 0) < 5:
|
| 649 |
+
logger.error(f"[PROJECT APPROVAL] ERROR: Insufficient credits for user: {uid}, credits: {user_data.get('credits', 0) if user_data else 0}")
|
| 650 |
+
return jsonify({'error': 'Insufficient credits'}), 402
|
| 651 |
+
user_fetch_time = time.time() - user_fetch_start
|
| 652 |
+
logger.info(f"[PROJECT APPROVAL] User data fetch completed in {user_fetch_time:.3f}s, credits: {user_data.get('credits', 0)}")
|
| 653 |
|
| 654 |
+
# Project data fetch timing
|
| 655 |
+
project_fetch_start = time.time()
|
| 656 |
project_ref = db_ref.child(f'projects/{project_id}')
|
| 657 |
project_data = project_ref.get()
|
| 658 |
if not project_data or project_data.get('uid') != uid:
|
| 659 |
+
logger.error(f"[PROJECT APPROVAL] ERROR: Project not found or access denied - project_id: {project_id}, uid: {uid}")
|
| 660 |
return jsonify({'error': 'Project not found or access denied'}), 404
|
| 661 |
+
project_fetch_time = time.time() - project_fetch_start
|
| 662 |
+
logger.info(f"[PROJECT APPROVAL] Project data fetch completed in {project_fetch_time:.3f}s for project: {project_data.get('projectTitle', 'Unknown')}")
|
| 663 |
|
| 664 |
+
# Image download and processing timing
|
| 665 |
selected_option = request.json.get('selectedOption')
|
| 666 |
+
logger.info(f"[PROJECT APPROVAL] Selected option: {selected_option}")
|
| 667 |
+
|
| 668 |
+
image_download_start = time.time()
|
| 669 |
response = requests.get(project_data['userImageURL'])
|
| 670 |
+
image_download_time = time.time() - image_download_start
|
| 671 |
+
logger.info(f"[PROJECT APPROVAL] Image download completed in {image_download_time:.3f}s, size: {len(response.content)} bytes")
|
| 672 |
+
|
| 673 |
+
image_processing_start = time.time()
|
| 674 |
pil_image = Image.open(io.BytesIO(response.content)).convert('RGB')
|
| 675 |
+
image_processing_time = time.time() - image_processing_start
|
| 676 |
+
logger.info(f"[PROJECT APPROVAL] Image processing completed in {image_processing_time:.3f}s")
|
| 677 |
|
| 678 |
+
# Context preparation timing
|
| 679 |
+
context_start = time.time()
|
| 680 |
context = (
|
| 681 |
f"The user chose the upcycling project: '{selected_option}'."
|
| 682 |
if selected_option
|
|
|
|
| 697 |
1. First step instructions.
|
| 698 |
2. Second step instructions...
|
| 699 |
"""
|
| 700 |
+
context_time = time.time() - context_start
|
| 701 |
+
logger.info(f"[PROJECT APPROVAL] Context preparation completed in {context_time:.3f}s")
|
| 702 |
|
| 703 |
try:
|
| 704 |
+
# AI generation timing
|
| 705 |
+
ai_start = time.time()
|
| 706 |
+
logger.info(f"[PROJECT APPROVAL] Starting AI generation with model: {GENERATION_MODEL}")
|
| 707 |
+
|
| 708 |
chat = client.chats.create(
|
| 709 |
model=GENERATION_MODEL,
|
| 710 |
config=types.GenerateContentConfig(response_modalities=["Text", "Image"])
|
| 711 |
)
|
| 712 |
full_resp = chat.send_message([detailed_prompt, pil_image])
|
| 713 |
+
ai_time = time.time() - ai_start
|
| 714 |
+
logger.info(f"[PROJECT APPROVAL] AI generation completed in {ai_time:.3f}s")
|
| 715 |
+
|
| 716 |
+
# Response parsing timing
|
| 717 |
+
parsing_start = time.time()
|
| 718 |
gen_parts = full_resp.candidates[0].content.parts
|
| 719 |
|
| 720 |
combined_text = ""
|
|
|
|
| 727 |
inline_images.append(img)
|
| 728 |
|
| 729 |
combined_text = combined_text.strip()
|
| 730 |
+
parsing_time = time.time() - parsing_start
|
| 731 |
+
logger.info(f"[PROJECT APPROVAL] Response parsing completed in {parsing_time:.3f}s, found {len(inline_images)} images")
|
| 732 |
|
| 733 |
+
# Text extraction timing
|
| 734 |
+
extraction_start = time.time()
|
| 735 |
tools_section = re.search(r"TOOLS AND MATERIALS:\s*(.*?)\s*STEPS:", combined_text, re.DOTALL).group(1).strip()
|
| 736 |
steps_section = re.search(r"STEPS:\s*(.*)", combined_text, re.DOTALL).group(1).strip()
|
| 737 |
|
| 738 |
tools_list = [line.strip("- ").strip() for line in tools_section.split('\n') if line.strip()]
|
| 739 |
parsed_steps = parse_numbered_steps(steps_section)
|
| 740 |
+
extraction_time = time.time() - extraction_start
|
| 741 |
+
logger.info(f"[PROJECT APPROVAL] Text extraction completed in {extraction_time:.3f}s, tools: {len(tools_list)}, steps: {len(parsed_steps)}")
|
| 742 |
|
| 743 |
if len(parsed_steps) != len(inline_images):
|
| 744 |
+
logger.error(f"[PROJECT APPROVAL] ERROR: AI response mismatch - Steps: {len(parsed_steps)}, Images: {len(inline_images)}")
|
| 745 |
return jsonify({'error': 'AI response mismatch: Steps and images do not match.'}), 500
|
| 746 |
|
| 747 |
+
# Step processing timing
|
| 748 |
+
step_processing_start = time.time()
|
| 749 |
final_steps = []
|
| 750 |
+
total_upload_time = 0
|
| 751 |
+
total_tts_time = 0
|
| 752 |
+
|
| 753 |
for i, step_info in enumerate(parsed_steps):
|
| 754 |
+
logger.info(f"[PROJECT APPROVAL] Processing step {i+1}/{len(parsed_steps)}")
|
| 755 |
+
|
| 756 |
+
# Image upload timing
|
| 757 |
+
image_upload_start = time.time()
|
| 758 |
img_byte_arr = io.BytesIO()
|
| 759 |
inline_images[i].save(img_byte_arr, format='JPEG', optimize=True, quality=70)
|
| 760 |
img_path = f"users/{uid}/projects/{project_id}/steps/step_{i+1}_image.jpg"
|
| 761 |
img_url = upload_to_storage(img_byte_arr.getvalue(), img_path, 'image/jpeg')
|
| 762 |
+
image_upload_time = time.time() - image_upload_start
|
| 763 |
+
total_upload_time += image_upload_time
|
| 764 |
+
logger.info(f"[PROJECT APPROVAL] Step {i+1} image upload completed in {image_upload_time:.3f}s")
|
| 765 |
|
| 766 |
+
# TTS generation timing
|
| 767 |
+
tts_start = time.time()
|
| 768 |
narration_url = generate_tts_audio_and_upload(step_info['text'], uid, project_id, i + 1)
|
| 769 |
+
tts_time = time.time() - tts_start
|
| 770 |
+
total_tts_time += tts_time
|
| 771 |
+
logger.info(f"[PROJECT APPROVAL] Step {i+1} TTS generation completed in {tts_time:.3f}s")
|
| 772 |
+
|
| 773 |
step_info.update({
|
| 774 |
"imageUrl": img_url,
|
| 775 |
"narrationUrl": narration_url,
|
|
|
|
| 778 |
})
|
| 779 |
final_steps.append(step_info)
|
| 780 |
|
| 781 |
+
step_processing_time = time.time() - step_processing_start
|
| 782 |
+
logger.info(f"[PROJECT APPROVAL] All steps processing completed in {step_processing_time:.3f}s")
|
| 783 |
+
logger.info(f"[PROJECT APPROVAL] Total upload time: {total_upload_time:.3f}s, Total TTS time: {total_tts_time:.3f}s")
|
| 784 |
+
|
| 785 |
+
# Database update timing
|
| 786 |
+
db_update_start = time.time()
|
| 787 |
update_data = {
|
| 788 |
"status": "ready",
|
| 789 |
"toolsList": tools_list,
|
|
|
|
| 791 |
"selectedOption": selected_option or ""
|
| 792 |
}
|
| 793 |
project_ref.update(update_data)
|
| 794 |
+
db_update_time = time.time() - db_update_start
|
| 795 |
+
logger.info(f"[PROJECT APPROVAL] Database update completed in {db_update_time:.3f}s")
|
| 796 |
|
| 797 |
+
# Final project fetch timing
|
| 798 |
+
final_fetch_start = time.time()
|
| 799 |
updated_project = project_ref.get()
|
| 800 |
updated_project["projectId"] = project_id
|
| 801 |
+
final_fetch_time = time.time() - final_fetch_start
|
| 802 |
+
logger.info(f"[PROJECT APPROVAL] Final project fetch completed in {final_fetch_time:.3f}s")
|
| 803 |
+
|
| 804 |
+
# Credits deduction timing
|
| 805 |
+
credits_update_start = time.time()
|
| 806 |
+
user_ref.update({'credits': user_data.get('credits', 0) - 5})
|
| 807 |
+
credits_update_time = time.time() - credits_update_start
|
| 808 |
+
logger.info(f"[PROJECT APPROVAL] Credits update completed in {credits_update_time:.3f}s")
|
| 809 |
+
|
| 810 |
+
# Total time calculation
|
| 811 |
+
total_time = time.time() - start_time
|
| 812 |
+
logger.info(f"[PROJECT APPROVAL] SUCCESS: Project approval completed in {total_time:.3f}s")
|
| 813 |
+
logger.info(f"[PROJECT APPROVAL] TIMING BREAKDOWN:")
|
| 814 |
+
logger.info(f"[PROJECT APPROVAL] - Authorization: {auth_time:.3f}s")
|
| 815 |
+
logger.info(f"[PROJECT APPROVAL] - User fetch: {user_fetch_time:.3f}s")
|
| 816 |
+
logger.info(f"[PROJECT APPROVAL] - Project fetch: {project_fetch_time:.3f}s")
|
| 817 |
+
logger.info(f"[PROJECT APPROVAL] - Image download: {image_download_time:.3f}s")
|
| 818 |
+
logger.info(f"[PROJECT APPROVAL] - Image processing: {image_processing_time:.3f}s")
|
| 819 |
+
logger.info(f"[PROJECT APPROVAL] - Context prep: {context_time:.3f}s")
|
| 820 |
+
logger.info(f"[PROJECT APPROVAL] - AI generation: {ai_time:.3f}s")
|
| 821 |
+
logger.info(f"[PROJECT APPROVAL] - Response parsing: {parsing_time:.3f}s")
|
| 822 |
+
logger.info(f"[PROJECT APPROVAL] - Text extraction: {extraction_time:.3f}s")
|
| 823 |
+
logger.info(f"[PROJECT APPROVAL] - Step processing: {step_processing_time:.3f}s")
|
| 824 |
+
logger.info(f"[PROJECT APPROVAL] - Total uploads: {total_upload_time:.3f}s")
|
| 825 |
+
logger.info(f"[PROJECT APPROVAL] - Total TTS: {total_tts_time:.3f}s")
|
| 826 |
+
logger.info(f"[PROJECT APPROVAL] - DB update: {db_update_time:.3f}s")
|
| 827 |
+
logger.info(f"[PROJECT APPROVAL] - Final fetch: {final_fetch_time:.3f}s")
|
| 828 |
+
logger.info(f"[PROJECT APPROVAL] - Credits update: {credits_update_time:.3f}s")
|
| 829 |
+
|
| 830 |
return jsonify(updated_project)
|
| 831 |
|
| 832 |
except Exception as e:
|
| 833 |
+
total_time = time.time() - start_time
|
| 834 |
+
logger.error(f"[PROJECT APPROVAL] ERROR: Exception occurred after {total_time:.3f}s: {e}")
|
| 835 |
+
logger.error(f"[PROJECT APPROVAL] Error type: {type(e).__name__}")
|
| 836 |
+
logger.error(f"[PROJECT APPROVAL] Project ID: {project_id}, User ID: {uid}")
|
| 837 |
+
import traceback
|
| 838 |
+
logger.error(f"[PROJECT APPROVAL] Full traceback: {traceback.format_exc()}")
|
| 839 |
return jsonify({'error': str(e)}), 500
|
| 840 |
|
| 841 |
@app.route('/api/projects', methods=['GET'])
|