Update main.py
Browse files
main.py
CHANGED
|
@@ -663,25 +663,55 @@ def call_gemini(prompt_text, is_json_output_expected=True):
|
|
| 663 |
{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
| 664 |
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
| 665 |
]
|
| 666 |
-
|
| 667 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 668 |
|
| 669 |
if not response.parts:
|
| 670 |
reason = "Unknown"
|
| 671 |
try:
|
| 672 |
reason = response.prompt_feedback.block_reason.name if hasattr(response, 'prompt_feedback') and response.prompt_feedback.block_reason else None
|
| 673 |
-
if not reason and response.candidates:
|
| 674 |
-
|
|
|
|
|
|
|
| 675 |
return None, f"AI response empty or blocked. Reason: {reason}"
|
|
|
|
| 676 |
if not hasattr(response, 'text') or not response.text:
|
| 677 |
return None, "AI response text empty."
|
| 678 |
|
| 679 |
if is_json_output_expected:
|
| 680 |
-
|
| 681 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 682 |
return parsed_json, None
|
| 683 |
else:
|
| 684 |
-
return
|
|
|
|
| 685 |
except Exception as e:
|
| 686 |
app.logger.error(f"Gemini API Call Error: {e} (Type: {type(e).__name__})")
|
| 687 |
return None, f"Gemini API Call Error: {e}"
|
|
@@ -703,6 +733,8 @@ JSON Output:
|
|
| 703 |
"""
|
| 704 |
return call_gemini(prompt, is_json_output_expected=True)
|
| 705 |
|
|
|
|
|
|
|
| 706 |
def generate_quiz_questions_gemini(members, relationships, num_questions=3):
|
| 707 |
if not members or len(members) < 2: return None, "Need at least 2 members for a quiz."
|
| 708 |
id_to_name = {p["id"]: p.get("name", "Unknown") for p in members if isinstance(p, dict)}
|
|
@@ -1498,24 +1530,42 @@ def get_proposal_diff_api(proposer_phone_to_review):
|
|
| 1498 |
|
| 1499 |
# --- AI Feature Endpoints (Protected as they operate on user's tree or consume user quota) ---
|
| 1500 |
@app.route('/ai/build_tree_from_text', methods=['POST'])
|
| 1501 |
-
@token_required
|
| 1502 |
def ai_build_tree_api():
|
| 1503 |
-
|
| 1504 |
-
|
| 1505 |
-
|
| 1506 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1507 |
|
| 1508 |
req_data = request.json
|
| 1509 |
if not req_data or "description" not in req_data:
|
| 1510 |
return jsonify({"error": "Missing description in request body"}), 400
|
|
|
|
| 1511 |
description = req_data["description"]
|
|
|
|
|
|
|
|
|
|
| 1512 |
|
| 1513 |
ai_result, error = generate_tree_from_description_gemini(description)
|
| 1514 |
if error:
|
| 1515 |
return jsonify({"error": "AI analysis failed", "detail": error}), 500
|
|
|
|
| 1516 |
return jsonify(ai_result), 200
|
| 1517 |
|
| 1518 |
|
|
|
|
| 1519 |
@app.route('/tree/<string:owner_phone_of_tree>/ai_merge_suggestions', methods=['POST'])
|
| 1520 |
@token_required
|
| 1521 |
@phone_required
|
|
|
|
| 663 |
{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
| 664 |
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
| 665 |
]
|
| 666 |
+
|
| 667 |
+
# Fixed: Use proper GenerationConfig parameters
|
| 668 |
+
gen_config = None
|
| 669 |
+
if is_json_output_expected:
|
| 670 |
+
gen_config = genai.types.GenerationConfig(
|
| 671 |
+
temperature=0.2, # Lower temperature for more consistent JSON
|
| 672 |
+
top_p=0.8,
|
| 673 |
+
top_k=40,
|
| 674 |
+
max_output_tokens=2048,
|
| 675 |
+
)
|
| 676 |
+
|
| 677 |
+
response = model.generate_content(
|
| 678 |
+
prompt_text,
|
| 679 |
+
generation_config=gen_config,
|
| 680 |
+
safety_settings=safety_settings
|
| 681 |
+
)
|
| 682 |
|
| 683 |
if not response.parts:
|
| 684 |
reason = "Unknown"
|
| 685 |
try:
|
| 686 |
reason = response.prompt_feedback.block_reason.name if hasattr(response, 'prompt_feedback') and response.prompt_feedback.block_reason else None
|
| 687 |
+
if not reason and response.candidates:
|
| 688 |
+
reason = response.candidates[0].finish_reason.name
|
| 689 |
+
except Exception:
|
| 690 |
+
pass
|
| 691 |
return None, f"AI response empty or blocked. Reason: {reason}"
|
| 692 |
+
|
| 693 |
if not hasattr(response, 'text') or not response.text:
|
| 694 |
return None, "AI response text empty."
|
| 695 |
|
| 696 |
if is_json_output_expected:
|
| 697 |
+
# Clean the response text to extract JSON
|
| 698 |
+
response_text = response.text.strip()
|
| 699 |
+
# Remove markdown code blocks if present
|
| 700 |
+
if response_text.startswith('```json'):
|
| 701 |
+
response_text = response_text[7:]
|
| 702 |
+
if response_text.startswith('```'):
|
| 703 |
+
response_text = response_text[3:]
|
| 704 |
+
if response_text.endswith('```'):
|
| 705 |
+
response_text = response_text[:-3]
|
| 706 |
+
response_text = response_text.strip()
|
| 707 |
+
|
| 708 |
+
parsed_json, err = safe_json_loads(response_text)
|
| 709 |
+
if err:
|
| 710 |
+
return None, f"AI response received, but failed to parse as JSON: {err}. Raw: {response_text[:500]}..."
|
| 711 |
return parsed_json, None
|
| 712 |
else:
|
| 713 |
+
return response_text, None
|
| 714 |
+
|
| 715 |
except Exception as e:
|
| 716 |
app.logger.error(f"Gemini API Call Error: {e} (Type: {type(e).__name__})")
|
| 717 |
return None, f"Gemini API Call Error: {e}"
|
|
|
|
| 733 |
"""
|
| 734 |
return call_gemini(prompt, is_json_output_expected=True)
|
| 735 |
|
| 736 |
+
|
| 737 |
+
|
| 738 |
def generate_quiz_questions_gemini(members, relationships, num_questions=3):
|
| 739 |
if not members or len(members) < 2: return None, "Need at least 2 members for a quiz."
|
| 740 |
id_to_name = {p["id"]: p.get("name", "Unknown") for p in members if isinstance(p, dict)}
|
|
|
|
| 1530 |
|
| 1531 |
# --- AI Feature Endpoints (Protected as they operate on user's tree or consume user quota) ---
|
| 1532 |
@app.route('/ai/build_tree_from_text', methods=['POST'])
|
| 1533 |
+
@token_required
|
| 1534 |
def ai_build_tree_api():
|
| 1535 |
+
"""
|
| 1536 |
+
Endpoint to analyze family description text and extract people and relationships.
|
| 1537 |
+
|
| 1538 |
+
Request body:
|
| 1539 |
+
{
|
| 1540 |
+
"description": "Family description text here..."
|
| 1541 |
+
}
|
| 1542 |
+
|
| 1543 |
+
Response:
|
| 1544 |
+
{
|
| 1545 |
+
"people": [...],
|
| 1546 |
+
"relationships": [...]
|
| 1547 |
+
}
|
| 1548 |
+
"""
|
| 1549 |
+
if api_key_error:
|
| 1550 |
+
return jsonify({"error": "Gemini API not configured"}), 503
|
| 1551 |
|
| 1552 |
req_data = request.json
|
| 1553 |
if not req_data or "description" not in req_data:
|
| 1554 |
return jsonify({"error": "Missing description in request body"}), 400
|
| 1555 |
+
|
| 1556 |
description = req_data["description"]
|
| 1557 |
+
|
| 1558 |
+
if not description.strip():
|
| 1559 |
+
return jsonify({"error": "Description cannot be empty"}), 400
|
| 1560 |
|
| 1561 |
ai_result, error = generate_tree_from_description_gemini(description)
|
| 1562 |
if error:
|
| 1563 |
return jsonify({"error": "AI analysis failed", "detail": error}), 500
|
| 1564 |
+
|
| 1565 |
return jsonify(ai_result), 200
|
| 1566 |
|
| 1567 |
|
| 1568 |
+
|
| 1569 |
@app.route('/tree/<string:owner_phone_of_tree>/ai_merge_suggestions', methods=['POST'])
|
| 1570 |
@token_required
|
| 1571 |
@phone_required
|