Spaces:
Running
Running
Update main.py
Browse files
main.py
CHANGED
|
@@ -568,148 +568,116 @@ def create_project():
|
|
| 568 |
def approve_project_plan(project_id):
|
| 569 |
uid = verify_token(request.headers.get('Authorization'))
|
| 570 |
if not uid: return jsonify({'error': 'Unauthorized'}), 401
|
| 571 |
-
|
| 572 |
project_ref = db_ref.child(f'projects/{project_id}')
|
| 573 |
project_data = project_ref.get()
|
| 574 |
if not project_data or project_data.get('uid') != uid:
|
| 575 |
return jsonify({'error': 'Project not found or access denied'}), 404
|
| 576 |
|
| 577 |
selected_option = request.json.get('selectedOption')
|
| 578 |
-
|
| 579 |
-
#
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
1. First step instructions.
|
| 607 |
-
2. Second step instructions..."""
|
| 608 |
|
| 609 |
try:
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 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
|
| 633 |
combined_text += part.text + "\n"
|
| 634 |
-
if
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
print(f"Failed to process image: {e}")
|
| 640 |
-
continue
|
| 641 |
-
|
| 642 |
combined_text = combined_text.strip()
|
| 643 |
-
|
| 644 |
-
#
|
| 645 |
-
|
| 646 |
-
|
| 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':
|
| 660 |
|
| 661 |
-
# Sequential processing with progress tracking
|
| 662 |
final_steps = []
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 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(
|
| 712 |
-
return jsonify({'error': f"Failed to generate detailed guide: {
|
| 713 |
|
| 714 |
@app.route('/api/projects', methods=['GET'])
|
| 715 |
def list_projects():
|
|
|
|
| 568 |
def approve_project_plan(project_id):
|
| 569 |
uid = verify_token(request.headers.get('Authorization'))
|
| 570 |
if not uid: return jsonify({'error': 'Unauthorized'}), 401
|
| 571 |
+
|
| 572 |
project_ref = db_ref.child(f'projects/{project_id}')
|
| 573 |
project_data = project_ref.get()
|
| 574 |
if not project_data or project_data.get('uid') != uid:
|
| 575 |
return jsonify({'error': 'Project not found or access denied'}), 404
|
| 576 |
|
| 577 |
selected_option = request.json.get('selectedOption')
|
| 578 |
+
|
| 579 |
+
# 1) Download & compress the user’s image before sending it off to Gemini
|
| 580 |
+
response = requests.get(project_data['userImageURL'])
|
| 581 |
+
pil_image = Image.open(io.BytesIO(response.content)).convert("RGB")
|
| 582 |
+
pil_image.thumbnail((1024, 1024)) # max‐side 1024px
|
| 583 |
+
buf = io.BytesIO()
|
| 584 |
+
pil_image.save(buf, format='JPEG', quality=75, optimize=True)
|
| 585 |
+
buf.seek(0)
|
| 586 |
+
compressed_image = Image.open(buf)
|
| 587 |
+
|
| 588 |
+
context = (f"The user chose the upcycling project: '{selected_option}'."
|
| 589 |
+
if selected_option
|
| 590 |
+
else f"The user has approved the plan for '{project_data['projectTitle']}'.")
|
| 591 |
+
|
| 592 |
+
detailed_prompt = f"""
|
| 593 |
+
You are a DIY expert. The user wants to proceed with the project titled "{project_data['projectTitle']}".
|
| 594 |
+
{context}
|
| 595 |
+
Provide a detailed guide. For each step, you MUST provide a simple, clear illustrative image.
|
| 596 |
+
Format your response EXACTLY like this:
|
| 597 |
+
|
| 598 |
+
TOOLS AND MATERIALS:
|
| 599 |
+
- Tool A
|
| 600 |
+
- Material B
|
| 601 |
+
|
| 602 |
+
STEPS(Maximum 7 steps):
|
| 603 |
+
1. First step instructions.
|
| 604 |
+
2. Second step instructions...
|
| 605 |
+
"""
|
|
|
|
|
|
|
| 606 |
|
| 607 |
try:
|
| 608 |
+
chat = client.chats.create(
|
| 609 |
+
model=GENERATION_MODEL,
|
| 610 |
+
config=types.GenerateContentConfig(response_modalities=["Text", "Image"])
|
| 611 |
+
)
|
| 612 |
+
full_resp = chat.send_message([detailed_prompt, compressed_image])
|
| 613 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
gen_parts = full_resp.candidates[0].content.parts
|
|
|
|
| 615 |
combined_text = ""
|
| 616 |
inline_images = []
|
| 617 |
+
|
|
|
|
| 618 |
for part in gen_parts:
|
| 619 |
+
if part.text:
|
| 620 |
combined_text += part.text + "\n"
|
| 621 |
+
if part.inline_data:
|
| 622 |
+
img = Image.open(BytesIO(part.inline_data.data)).convert("RGB")
|
| 623 |
+
# 2) Immediately downscale each AI‐generated image
|
| 624 |
+
img.thumbnail((800, 800))
|
| 625 |
+
inline_images.append(img)
|
|
|
|
|
|
|
|
|
|
| 626 |
combined_text = combined_text.strip()
|
| 627 |
+
|
| 628 |
+
# parse out tools + steps
|
| 629 |
+
tools_section = re.search(r"TOOLS AND MATERIALS:\s*(.*?)\s*STEPS:", combined_text, re.DOTALL).group(1).strip()
|
| 630 |
+
steps_section = re.search(r"STEPS:\s*(.*)", combined_text, re.DOTALL).group(1).strip()
|
| 631 |
+
tools_list = [line.strip('- ').strip() for line in tools_section.splitlines() if line.strip()]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 632 |
parsed_steps = parse_numbered_steps(steps_section)
|
| 633 |
|
| 634 |
+
if len(parsed_steps) != len(inline_images):
|
| 635 |
+
return jsonify({'error': 'AI response mismatch: Steps and images do not match.'}), 500
|
| 636 |
|
|
|
|
| 637 |
final_steps = []
|
| 638 |
+
for i, step_info in enumerate(parsed_steps):
|
| 639 |
+
# 3) Save JPEG at 70% quality
|
| 640 |
+
img_byte_arr = io.BytesIO()
|
| 641 |
+
inline_images[i].save(
|
| 642 |
+
img_byte_arr,
|
| 643 |
+
format='JPEG',
|
| 644 |
+
quality=70,
|
| 645 |
+
optimize=True
|
| 646 |
+
)
|
| 647 |
+
img_path = f"users/{uid}/projects/{project_id}/steps/step_{i+1}_image.jpg"
|
| 648 |
+
img_url = upload_to_storage(img_byte_arr.getvalue(), img_path, 'image/jpeg')
|
| 649 |
+
|
| 650 |
+
# 4) Generate and compress TTS audio to mp3 @ 64kbps
|
| 651 |
+
raw_audio = generate_tts_audio(step_info['text']) # returns raw bytes
|
| 652 |
+
from pydub import AudioSegment
|
| 653 |
+
sound = AudioSegment.from_file(io.BytesIO(raw_audio), format="wav")
|
| 654 |
+
mp3_buf = io.BytesIO()
|
| 655 |
+
sound.export(mp3_buf, format="mp3", bitrate="64k")
|
| 656 |
+
mp3_buf.seek(0)
|
| 657 |
+
narration_url = upload_to_storage(mp3_buf.read(),
|
| 658 |
+
f"users/{uid}/projects/{project_id}/steps/step_{i+1}_tts.mp3",
|
| 659 |
+
'audio/mpeg')
|
| 660 |
+
|
| 661 |
+
step_info.update({
|
| 662 |
+
"imageUrl": img_url,
|
| 663 |
+
"narrationUrl": narration_url,
|
| 664 |
+
"isDone": False,
|
| 665 |
+
"notes": ""
|
| 666 |
+
})
|
| 667 |
+
final_steps.append(step_info)
|
| 668 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 669 |
update_data = {
|
| 670 |
+
"status": "ready",
|
| 671 |
+
"toolsList": tools_list,
|
| 672 |
+
"steps": final_steps,
|
| 673 |
"selectedOption": selected_option or ""
|
| 674 |
}
|
|
|
|
| 675 |
project_ref.update(update_data)
|
|
|
|
| 676 |
return jsonify({"success": True, **update_data})
|
| 677 |
|
| 678 |
except Exception as e:
|
| 679 |
+
print(traceback.format_exc())
|
| 680 |
+
return jsonify({'error': f"Failed to generate detailed guide: {e}"}), 500
|
| 681 |
|
| 682 |
@app.route('/api/projects', methods=['GET'])
|
| 683 |
def list_projects():
|