Spaces:
Sleeping
Sleeping
patrykslomka Claude Opus 4.6 commited on
Commit ·
9028070
1
Parent(s): 4a1a4b0
fix: resolve circular import, analysis API, locale mixing, and UX bugs
Browse files- Extract limiter to extensions.py to break circular import between app.py and blueprints
- Fix generate_study_explanation calling .get() on raw Response (needs .json() first)
- Pass locale parameter through /analyze and learning quiz fallback
- Change /uploads/<filename> from require_auth to optional_auth for <img> tags
- Prevent learn card collapse when selecting text or clicking inside expanded content
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- app.py +1 -8
- blueprints/analysis.py +5 -4
- blueprints/learning.py +2 -2
- extensions.py +8 -0
- src/claude_integration.py +5 -1
- static/js/learning.js +6 -1
app.py
CHANGED
|
@@ -4,14 +4,7 @@ import logging
|
|
| 4 |
import torch
|
| 5 |
from flask import Flask, jsonify
|
| 6 |
from flask_cors import CORS
|
| 7 |
-
from
|
| 8 |
-
from flask_limiter.util import get_remote_address
|
| 9 |
-
|
| 10 |
-
limiter = Limiter(
|
| 11 |
-
get_remote_address,
|
| 12 |
-
default_limits=["60 per minute"],
|
| 13 |
-
storage_uri="memory://",
|
| 14 |
-
)
|
| 15 |
from dotenv import load_dotenv
|
| 16 |
from src.dental_ontology import DentalOntology
|
| 17 |
from src.claude_integration import ClaudeAssistant
|
|
|
|
| 4 |
import torch
|
| 5 |
from flask import Flask, jsonify
|
| 6 |
from flask_cors import CORS
|
| 7 |
+
from extensions import limiter
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
from dotenv import load_dotenv
|
| 9 |
from src.dental_ontology import DentalOntology
|
| 10 |
from src.claude_integration import ClaudeAssistant
|
blueprints/analysis.py
CHANGED
|
@@ -17,8 +17,8 @@ from flask import (
|
|
| 17 |
from werkzeug.utils import secure_filename
|
| 18 |
from PIL import Image, ImageDraw, ImageFont
|
| 19 |
|
| 20 |
-
from
|
| 21 |
-
from src.auth import require_auth
|
| 22 |
from src.analysis_helpers import allowed_file, ALLOWED_EXTENSIONS, normalize_image_orientation
|
| 23 |
from src.analysis_service import _run_analysis
|
| 24 |
from src.image_processing import (
|
|
@@ -65,7 +65,7 @@ def index():
|
|
| 65 |
# ---------------------------------------------------------------------------
|
| 66 |
|
| 67 |
@analysis_bp.route('/uploads/<filename>')
|
| 68 |
-
@
|
| 69 |
def uploaded_file(filename):
|
| 70 |
filename = secure_filename(filename)
|
| 71 |
if not filename:
|
|
@@ -100,7 +100,8 @@ def analyze():
|
|
| 100 |
normalize_image_orientation(file_path)
|
| 101 |
|
| 102 |
try:
|
| 103 |
-
|
|
|
|
| 104 |
_save_analysis_to_firestore(
|
| 105 |
current_app.extensions.get('firestore'),
|
| 106 |
g.user['clerk_id'], filename, result,
|
|
|
|
| 17 |
from werkzeug.utils import secure_filename
|
| 18 |
from PIL import Image, ImageDraw, ImageFont
|
| 19 |
|
| 20 |
+
from extensions import limiter
|
| 21 |
+
from src.auth import require_auth, optional_auth
|
| 22 |
from src.analysis_helpers import allowed_file, ALLOWED_EXTENSIONS, normalize_image_orientation
|
| 23 |
from src.analysis_service import _run_analysis
|
| 24 |
from src.image_processing import (
|
|
|
|
| 65 |
# ---------------------------------------------------------------------------
|
| 66 |
|
| 67 |
@analysis_bp.route('/uploads/<filename>')
|
| 68 |
+
@optional_auth
|
| 69 |
def uploaded_file(filename):
|
| 70 |
filename = secure_filename(filename)
|
| 71 |
if not filename:
|
|
|
|
| 100 |
normalize_image_orientation(file_path)
|
| 101 |
|
| 102 |
try:
|
| 103 |
+
locale = request.form.get('locale', 'en')
|
| 104 |
+
result = _run_analysis(file_path, filename, False, locale=locale)
|
| 105 |
_save_analysis_to_firestore(
|
| 106 |
current_app.extensions.get('firestore'),
|
| 107 |
g.user['clerk_id'], filename, result,
|
blueprints/learning.py
CHANGED
|
@@ -6,7 +6,7 @@ import random
|
|
| 6 |
from flask import Blueprint, jsonify, request, current_app, g, send_from_directory, abort
|
| 7 |
from werkzeug.utils import secure_filename
|
| 8 |
|
| 9 |
-
from
|
| 10 |
from src.auth import optional_auth
|
| 11 |
|
| 12 |
learning_bp = Blueprint('learning', __name__, url_prefix='/learn')
|
|
@@ -137,7 +137,7 @@ def check_quiz_answer():
|
|
| 137 |
logger.info('Quiz check: no cached predictions, running analysis for %s', filename)
|
| 138 |
try:
|
| 139 |
from src.analysis_service import _run_analysis
|
| 140 |
-
result = _run_analysis(image_path, filename, use_vision=False)
|
| 141 |
predictions = result.get('predictions', [])
|
| 142 |
# Cache the predictions for future quiz use
|
| 143 |
with open(predictions_path, 'w') as f:
|
|
|
|
| 6 |
from flask import Blueprint, jsonify, request, current_app, g, send_from_directory, abort
|
| 7 |
from werkzeug.utils import secure_filename
|
| 8 |
|
| 9 |
+
from extensions import limiter
|
| 10 |
from src.auth import optional_auth
|
| 11 |
|
| 12 |
learning_bp = Blueprint('learning', __name__, url_prefix='/learn')
|
|
|
|
| 137 |
logger.info('Quiz check: no cached predictions, running analysis for %s', filename)
|
| 138 |
try:
|
| 139 |
from src.analysis_service import _run_analysis
|
| 140 |
+
result = _run_analysis(image_path, filename, use_vision=False, locale=locale)
|
| 141 |
predictions = result.get('predictions', [])
|
| 142 |
# Cache the predictions for future quiz use
|
| 143 |
with open(predictions_path, 'w') as f:
|
extensions.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask_limiter import Limiter
|
| 2 |
+
from flask_limiter.util import get_remote_address
|
| 3 |
+
|
| 4 |
+
limiter = Limiter(
|
| 5 |
+
get_remote_address,
|
| 6 |
+
default_limits=["60 per minute"],
|
| 7 |
+
storage_uri="memory://",
|
| 8 |
+
)
|
src/claude_integration.py
CHANGED
|
@@ -466,7 +466,11 @@ Return ONLY the JSON object, no additional text."""
|
|
| 466 |
"system": system_msg,
|
| 467 |
"messages": [{"role": "user", "content": user_msg}],
|
| 468 |
}
|
| 469 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 470 |
content = result.get("content", [])
|
| 471 |
if not content:
|
| 472 |
return None
|
|
|
|
| 466 |
"system": system_msg,
|
| 467 |
"messages": [{"role": "user", "content": user_msg}],
|
| 468 |
}
|
| 469 |
+
response = self._api_request(data, timeout=30)
|
| 470 |
+
if response.status_code != 200:
|
| 471 |
+
logger.error("Study explanation API error: %s %s", response.status_code, response.text[:200])
|
| 472 |
+
return None
|
| 473 |
+
result = response.json()
|
| 474 |
content = result.get("content", [])
|
| 475 |
if not content:
|
| 476 |
return None
|
static/js/learning.js
CHANGED
|
@@ -364,7 +364,12 @@ export async function initLearnBrowser() {
|
|
| 364 |
<p class="learn-card-preview">${cond.definition?.substring(0, 100) || ''}...</p>
|
| 365 |
`;
|
| 366 |
|
| 367 |
-
card.addEventListener('click', () =>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 368 |
grid.appendChild(card);
|
| 369 |
}
|
| 370 |
|
|
|
|
| 364 |
<p class="learn-card-preview">${cond.definition?.substring(0, 100) || ''}...</p>
|
| 365 |
`;
|
| 366 |
|
| 367 |
+
card.addEventListener('click', (e) => {
|
| 368 |
+
// Don't toggle if user is selecting text or clicked inside expanded content
|
| 369 |
+
if (window.getSelection().toString()) return;
|
| 370 |
+
if (card.classList.contains('expanded') && e.target.closest('.learn-card-content')) return;
|
| 371 |
+
expandLearnCard(card, cond.key, locale);
|
| 372 |
+
});
|
| 373 |
grid.appendChild(card);
|
| 374 |
}
|
| 375 |
|