mirror of
https://github.com/R0m1k3/Reviflow.git
synced 2026-10-14 10:47:57 +02:00
175 lines
5.8 KiB
Python
175 lines
5.8 KiB
Python
"""Ingest module router for image analysis."""
|
|
import json
|
|
import asyncio
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from fastapi.responses import StreamingResponse
|
|
from app.modules.ingest.schemas import AnalyzeRequest, AnalyzeResponse, AnalyzeError
|
|
from app.modules.ingest.service import analyze_documents
|
|
from app.modules.auth.service import current_active_user
|
|
from typing import Optional
|
|
from app.modules.auth.models import User, UserRole
|
|
|
|
from app.core.db import get_async_session
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select
|
|
from app.config import settings
|
|
|
|
router = APIRouter()
|
|
|
|
async def get_effective_api_key(user: User, db: AsyncSession) -> Optional[str]:
|
|
"""Retrieve the API key from user, parent, or global settings."""
|
|
# 1. User specific key
|
|
if user.openrouter_api_key:
|
|
return user.openrouter_api_key
|
|
|
|
# 2. Parent's key (if learner)
|
|
if user.parent_id:
|
|
statement = select(User).where(User.id == user.parent_id)
|
|
result = await db.execute(statement)
|
|
parent = result.scalar_one_or_none()
|
|
if parent and parent.openrouter_api_key:
|
|
return parent.openrouter_api_key
|
|
|
|
# 3. Global settings key
|
|
if settings.OPENROUTER_API_KEY:
|
|
return settings.OPENROUTER_API_KEY
|
|
|
|
return None
|
|
|
|
|
|
@router.post(
|
|
"/analyze",
|
|
response_model=AnalyzeResponse,
|
|
responses={
|
|
400: {"model": AnalyzeError, "description": "Invalid request"},
|
|
401: {"model": AnalyzeError, "description": "No API key configured"},
|
|
500: {"model": AnalyzeError, "description": "AI service error"}
|
|
}
|
|
)
|
|
async def analyze_image_endpoint(
|
|
request: AnalyzeRequest,
|
|
user: User = Depends(current_active_user),
|
|
db: AsyncSession = Depends(get_async_session)
|
|
):
|
|
"""
|
|
Analyze an image using AI Vision to extract lesson content.
|
|
|
|
Requires a valid OpenRouter API key configured in user settings.
|
|
"""
|
|
# Determine which API key to use
|
|
api_key = await get_effective_api_key(user, db)
|
|
|
|
if not api_key:
|
|
raise HTTPException(
|
|
status_code=401,
|
|
detail="No OpenRouter API key configured. Please add your API key in Settings or contact your administrator."
|
|
)
|
|
|
|
if not request.images_base64:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="No images provided"
|
|
)
|
|
|
|
try:
|
|
result, math_safety_triggered = await analyze_documents(
|
|
request.images_base64,
|
|
api_key
|
|
)
|
|
|
|
# Update usage
|
|
usage = result.get("usage", {})
|
|
user.total_tokens_used += usage.get("total_tokens", 0)
|
|
# Approximate cost (0.1$ / 1M tokens for Flash)
|
|
user.total_cost_usd += usage.get("total_tokens", 0) * 0.0000001
|
|
|
|
db.add(user)
|
|
await db.commit()
|
|
|
|
return AnalyzeResponse(**result)
|
|
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"AI analysis failed: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.post("/analyze-stream")
|
|
async def analyze_image_stream(
|
|
request: AnalyzeRequest,
|
|
user: User = Depends(current_active_user),
|
|
db: AsyncSession = Depends(get_async_session)
|
|
):
|
|
"""
|
|
Analyze an image with Server-Sent Events for progress updates.
|
|
|
|
Streams progress events: uploading -> reading -> analyzing -> complete
|
|
"""
|
|
# Determine which API key to use
|
|
api_key = await get_effective_api_key(user, db)
|
|
|
|
if not api_key:
|
|
raise HTTPException(
|
|
status_code=401,
|
|
detail="No OpenRouter API key configured. Please add your API key in Settings or contact your administrator."
|
|
)
|
|
|
|
if not request.images_base64:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="No images provided"
|
|
)
|
|
|
|
async def event_generator():
|
|
try:
|
|
# Step 1: Uploading
|
|
yield f"data: {json.dumps({'step': 'uploading', 'message': 'Téléchargement...', 'progress': 10})}\n\n"
|
|
await asyncio.sleep(0.1)
|
|
|
|
# Step 2: Reading (OCR)
|
|
msg = {'step': 'reading', 'message': "Lecture des documents...", 'progress': 30}
|
|
yield f"data: {json.dumps(msg)}\n\n"
|
|
await asyncio.sleep(0.1)
|
|
|
|
# Step 3: Analyzing
|
|
yield f"data: {json.dumps({'step': 'analyzing', 'message': 'Analyse IA en cours...', 'progress': 50})}\n\n"
|
|
|
|
# Actually perform the analysis
|
|
result, math_safety_triggered = await analyze_documents(
|
|
request.images_base64,
|
|
api_key
|
|
)
|
|
|
|
# Update usage
|
|
usage = result.get("usage", {})
|
|
user.total_tokens_used += usage.get("total_tokens", 0)
|
|
user.total_cost_usd += usage.get("total_tokens", 0) * 0.0000001
|
|
|
|
db.add(user)
|
|
await db.commit()
|
|
|
|
# Step 4: Synthesizing
|
|
yield f"data: {json.dumps({'step': 'synthesizing', 'message': 'Génération de la synthèse...', 'progress': 80})}\n\n"
|
|
await asyncio.sleep(0.1)
|
|
|
|
# Step 5: Complete
|
|
yield f"data: {json.dumps({'step': 'complete', 'message': 'Terminé!', 'progress': 100, 'result': result})}\n\n"
|
|
|
|
except Exception as e:
|
|
import traceback
|
|
error_msg = f"Error in analyze_image_stream: {str(e)}\n{traceback.format_exc()}"
|
|
print(error_msg) # Log to console
|
|
yield f"data: {json.dumps({'step': 'error', 'message': str(e), 'progress': 0})}\n\n"
|
|
|
|
return StreamingResponse(
|
|
event_generator(),
|
|
media_type="text/event-stream",
|
|
headers={
|
|
"Cache-Control": "no-cache",
|
|
"Connection": "keep-alive",
|
|
"X-Accel-Buffering": "no"
|
|
}
|
|
)
|
|
|