Spaces:
Running
Running
Update main.py
Browse files
main.py
CHANGED
|
@@ -240,38 +240,43 @@ def generate_master_blueprint_task(subject, flattened_data, uid, epiphany_id):
|
|
| 240 |
return None
|
| 241 |
|
| 242 |
#Prepare hi fidelity images to proper scale
|
| 243 |
-
def
|
| 244 |
"""
|
| 245 |
-
|
| 246 |
-
|
| 247 |
"""
|
| 248 |
-
size_mb = len(image_bytes) / (1024 * 1024)
|
| 249 |
-
if size_mb <= max_size_mb:
|
| 250 |
-
return image_bytes
|
| 251 |
-
|
| 252 |
-
logger.info(f"Vision Scaler: Image is {size_mb:.2f}MB. Optimizing for API...")
|
| 253 |
try:
|
| 254 |
img = Image.open(io.BytesIO(image_bytes))
|
| 255 |
|
| 256 |
-
#
|
| 257 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
w, h = img.size
|
| 259 |
-
if max(w, h) >
|
| 260 |
-
scale =
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
#
|
| 266 |
-
|
| 267 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
|
| 269 |
-
new_size_mb = len(new_bytes)/(1024*1024)
|
| 270 |
-
logger.info(f"Vision Scaler: Optimization complete. New size: {new_size_mb:.2f}MB")
|
| 271 |
-
return new_bytes
|
| 272 |
except Exception as e:
|
| 273 |
-
logger.error(f"Vision Scaler Failure: {e}")
|
| 274 |
-
|
|
|
|
| 275 |
# -----------------------------------------------------------------------------
|
| 276 |
# 4. PRIMARY ENDPOINTS: GENERATE & THEIA SWEEP
|
| 277 |
# -----------------------------------------------------------------------------
|
|
@@ -305,8 +310,11 @@ def generate_epiphany():
|
|
| 305 |
return jsonify({'error': 'Visual image is required.'}), 400
|
| 306 |
|
| 307 |
image_file = request.files['image']
|
| 308 |
-
|
|
|
|
|
|
|
| 309 |
pil_image = Image.open(io.BytesIO(image_bytes)).convert('RGB')
|
|
|
|
| 310 |
|
| 311 |
try:
|
| 312 |
# Step 1: Accurate Identification
|
|
@@ -410,7 +418,7 @@ def generate_epiphany():
|
|
| 410 |
|
| 411 |
@app.route('/api/epiphany/theia', methods=['POST'])
|
| 412 |
def theia_sweep():
|
| 413 |
-
"""Standalone Theia Mode: Bounding Box Annotations with
|
| 414 |
logger.info(">>> THEIA SWEEP INITIATED")
|
| 415 |
uid = verify_token(request.headers.get('Authorization'))
|
| 416 |
if not uid: return jsonify({'error': 'Unauthorized'}), 401
|
|
@@ -418,27 +426,32 @@ def theia_sweep():
|
|
| 418 |
epiphany_id = request.form.get('epiphanyId')
|
| 419 |
if not epiphany_id: return jsonify({'error': 'epiphanyId is required.'}), 400
|
| 420 |
|
| 421 |
-
# 1. Check
|
| 422 |
epiphany_ref = db_ref.child(f'epiphanies/{epiphany_id}')
|
| 423 |
existing_data = epiphany_ref.get() or {}
|
| 424 |
if 'annotations' in existing_data:
|
| 425 |
-
|
|
|
|
| 426 |
|
| 427 |
-
# 2. Check
|
| 428 |
user_ref = db_ref.child(f'users/{uid}')
|
| 429 |
user_data = user_ref.get() or {}
|
| 430 |
if user_data.get('credits', 0) < 4:
|
| 431 |
return jsonify({'error': 'Need 4 Sparks for a Theia Sweep.'}), 402
|
| 432 |
|
| 433 |
-
#
|
| 434 |
if 'image' not in request.files:
|
| 435 |
return jsonify({'error': 'image file is required.'}), 400
|
| 436 |
|
| 437 |
image_file = request.files['image']
|
| 438 |
raw_bytes = image_file.read()
|
| 439 |
|
| 440 |
-
#
|
| 441 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 442 |
|
| 443 |
subject = existing_data.get('subject', 'Complex System')
|
| 444 |
|
|
@@ -453,7 +466,7 @@ def theia_sweep():
|
|
| 453 |
"""
|
| 454 |
|
| 455 |
try:
|
| 456 |
-
|
| 457 |
res = client.models.generate_content(
|
| 458 |
model=ATHENA_FLASH,
|
| 459 |
contents=[sweep_prompt, pil_image],
|
|
@@ -464,19 +477,21 @@ def theia_sweep():
|
|
| 464 |
)
|
| 465 |
|
| 466 |
raw_json = res.text.strip()
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
|
|
|
| 470 |
|
| 471 |
annotations = json.loads(raw_json)
|
| 472 |
|
| 473 |
-
#
|
| 474 |
epiphany_ref.update({"annotations": annotations})
|
| 475 |
user_ref.update({'credits': user_data.get('credits', 0) - 4})
|
| 476 |
|
|
|
|
| 477 |
return jsonify({"annotations": annotations}), 200
|
| 478 |
except Exception as e:
|
| 479 |
-
logger.error(f"Theia Sweep Error: {e}")
|
| 480 |
return jsonify({'error': str(e)}), 500
|
| 481 |
|
| 482 |
@app.route('/api/epiphany/deep-dive', methods=['POST'])
|
|
|
|
| 240 |
return None
|
| 241 |
|
| 242 |
#Prepare hi fidelity images to proper scale
|
| 243 |
+
def prepare_vision_image(image_bytes):
|
| 244 |
"""
|
| 245 |
+
Resizes and optimizes the image to ensure it is under the 10MB Gemini limit.
|
| 246 |
+
Returns a PIL Image object ready for the SDK.
|
| 247 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
try:
|
| 249 |
img = Image.open(io.BytesIO(image_bytes))
|
| 250 |
|
| 251 |
+
# 1. Convert to RGB to strip Alpha channels (saves 25% space)
|
| 252 |
+
if img.mode != 'RGB':
|
| 253 |
+
img = img.convert('RGB')
|
| 254 |
+
|
| 255 |
+
# 2. If the file is huge, reduce the physical resolution.
|
| 256 |
+
# 2000px is more than enough for Athena to see components.
|
| 257 |
+
max_dimension = 2000
|
| 258 |
w, h = img.size
|
| 259 |
+
if max(w, h) > max_dimension:
|
| 260 |
+
scale = max_dimension / max(w, h)
|
| 261 |
+
new_size = (int(w * scale), int(h * scale))
|
| 262 |
+
img = img.resize(new_size, Image.Resampling.LANCZOS)
|
| 263 |
+
logger.info(f"Theia Vision: Resized from {w}x{h} to {new_size[0]}x{new_size[1]}")
|
| 264 |
+
|
| 265 |
+
# 3. Final Quality Check: If the image is still dense,
|
| 266 |
+
# we do an in-memory compression cycle to strip metadata.
|
| 267 |
+
buffer = io.BytesIO()
|
| 268 |
+
img.save(buffer, format="JPEG", quality=85, optimize=True)
|
| 269 |
+
final_img = Image.open(buffer)
|
| 270 |
+
|
| 271 |
+
final_size = len(buffer.getvalue())
|
| 272 |
+
logger.info(f"Theia Vision: Optimized payload size: {final_size / 1024 / 1024:.2f}MB")
|
| 273 |
+
|
| 274 |
+
return final_img, final_size
|
| 275 |
|
|
|
|
|
|
|
|
|
|
| 276 |
except Exception as e:
|
| 277 |
+
logger.error(f"Theia Vision Scaler Failure: {e}")
|
| 278 |
+
# Fallback to original
|
| 279 |
+
return Image.open(io.BytesIO(image_bytes))
|
| 280 |
# -----------------------------------------------------------------------------
|
| 281 |
# 4. PRIMARY ENDPOINTS: GENERATE & THEIA SWEEP
|
| 282 |
# -----------------------------------------------------------------------------
|
|
|
|
| 310 |
return jsonify({'error': 'Visual image is required.'}), 400
|
| 311 |
|
| 312 |
image_file = request.files['image']
|
| 313 |
+
raw_bytes = image_file.read()
|
| 314 |
+
# THE FIX:
|
| 315 |
+
image_bytes = prepare_vision_bytes(raw_bytes)
|
| 316 |
pil_image = Image.open(io.BytesIO(image_bytes)).convert('RGB')
|
| 317 |
+
|
| 318 |
|
| 319 |
try:
|
| 320 |
# Step 1: Accurate Identification
|
|
|
|
| 418 |
|
| 419 |
@app.route('/api/epiphany/theia', methods=['POST'])
|
| 420 |
def theia_sweep():
|
| 421 |
+
"""Standalone Theia Mode: Bounding Box Annotations with Automatic Image Scaling."""
|
| 422 |
logger.info(">>> THEIA SWEEP INITIATED")
|
| 423 |
uid = verify_token(request.headers.get('Authorization'))
|
| 424 |
if not uid: return jsonify({'error': 'Unauthorized'}), 401
|
|
|
|
| 426 |
epiphany_id = request.form.get('epiphanyId')
|
| 427 |
if not epiphany_id: return jsonify({'error': 'epiphanyId is required.'}), 400
|
| 428 |
|
| 429 |
+
# 1. Cache Check
|
| 430 |
epiphany_ref = db_ref.child(f'epiphanies/{epiphany_id}')
|
| 431 |
existing_data = epiphany_ref.get() or {}
|
| 432 |
if 'annotations' in existing_data:
|
| 433 |
+
logger.info(f"Theia: Returning cached annotations for {epiphany_id}")
|
| 434 |
+
return jsonify({"annotations": existing_data['annotations'], "status": "cached"}), 200
|
| 435 |
|
| 436 |
+
# 2. Credit Check
|
| 437 |
user_ref = db_ref.child(f'users/{uid}')
|
| 438 |
user_data = user_ref.get() or {}
|
| 439 |
if user_data.get('credits', 0) < 4:
|
| 440 |
return jsonify({'error': 'Need 4 Sparks for a Theia Sweep.'}), 402
|
| 441 |
|
| 442 |
+
# 3. Image Handling with Scaler
|
| 443 |
if 'image' not in request.files:
|
| 444 |
return jsonify({'error': 'image file is required.'}), 400
|
| 445 |
|
| 446 |
image_file = request.files['image']
|
| 447 |
raw_bytes = image_file.read()
|
| 448 |
|
| 449 |
+
# --- THE CRITICAL FIX ---
|
| 450 |
+
# We process the raw bytes into a scaled/optimized PIL Image
|
| 451 |
+
|
| 452 |
+
pil_image, scaled_size = prepare_vision_image(raw_bytes)
|
| 453 |
+
|
| 454 |
+
logger.info(f"Theia Post-Scale Size: {scaled_size / (1024 * 1024):.2f} MB")
|
| 455 |
|
| 456 |
subject = existing_data.get('subject', 'Complex System')
|
| 457 |
|
|
|
|
| 466 |
"""
|
| 467 |
|
| 468 |
try:
|
| 469 |
+
# Pass the optimized PIL image to the SDK
|
| 470 |
res = client.models.generate_content(
|
| 471 |
model=ATHENA_FLASH,
|
| 472 |
contents=[sweep_prompt, pil_image],
|
|
|
|
| 477 |
)
|
| 478 |
|
| 479 |
raw_json = res.text.strip()
|
| 480 |
+
if "```json" in raw_json:
|
| 481 |
+
raw_json = re.search(r'```json\n(.*?)\n```', raw_json, re.DOTALL).group(1)
|
| 482 |
+
elif "```" in raw_json:
|
| 483 |
+
raw_json = raw_json.replace("```", "").strip()
|
| 484 |
|
| 485 |
annotations = json.loads(raw_json)
|
| 486 |
|
| 487 |
+
# 4. Persistence & Deduction
|
| 488 |
epiphany_ref.update({"annotations": annotations})
|
| 489 |
user_ref.update({'credits': user_data.get('credits', 0) - 4})
|
| 490 |
|
| 491 |
+
logger.info(f"THEIA SUCCESS: {len(annotations)} annotations found.")
|
| 492 |
return jsonify({"annotations": annotations}), 200
|
| 493 |
except Exception as e:
|
| 494 |
+
logger.error(f"Theia Sweep Execution Error: {e}")
|
| 495 |
return jsonify({'error': str(e)}), 500
|
| 496 |
|
| 497 |
@app.route('/api/epiphany/deep-dive', methods=['POST'])
|