Spaces:
Sleeping
Sleeping
File size: 9,444 Bytes
e8d43fa 4fc93b8 484431d 7fd7594 e8d43fa a5c8ff6 ea1eac0 e8d43fa 4fc93b8 c0426da 4fc93b8 8e30b6a 4fc93b8 8e30b6a 4fc93b8 c0426da 94ddfa7 db0fed3 4fc93b8 e8d43fa 4fc93b8 1900ea7 4fc93b8 1900ea7 4fc93b8 7c5da40 c0426da 4fc93b8 484431d c0426da 4a1fdf8 c0426da 4a1fdf8 c0426da da164cc c0426da 484431d db0fed3 da164cc db0fed3 e8d43fa 94ddfa7 dcb5a1a 94ddfa7 dcb5a1a 7c5da40 e8d43fa 4fc93b8 7c5da40 94ddfa7 e8d43fa 94ddfa7 7c5da40 dcb5a1a e8d43fa 7c5da40 94ddfa7 7c5da40 e8d43fa dcb5a1a e8d43fa 7c5da40 db0fed3 7c5da40 e8d43fa 7c5da40 e8d43fa 484431d e8d43fa 7c5da40 e8d43fa 7c5da40 e8d43fa db0fed3 8e30b6a 7c5da40 db0fed3 7c5da40 da164cc c56dba2 2d188a5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | import asyncio
from collections import defaultdict
import logging
from fastapi import APIRouter, Depends, HTTPException, Request, status
from app.dependencies import verify_api_key
from app.services.queue import get_queue_service
from slowapi.errors import RateLimitExceeded
from limits import parse
from redis.exceptions import LockError
from app.models.schemas import (
AnalysisRequest,
AnalysisResponse,
ErrorResponse,
GuildConfigSchema,
HealthResponse,
TextAnalysisRequest,
ImageAnalysisRequest,
)
from app.services.download import download_file
from app.services.text_analyzer import analyze_text
from app.services.image_analyzer import analyze_image
from app.core.config import get_settings
from app.utils.exceptions import DeepfakeDetectionError, SetupRequiredError
from app.core.limiter import limiter
from app.config_manager import _load_all_configs, save_guild_config
logger = logging.getLogger(__name__)
router = APIRouter()
local_guild_locks = defaultdict(asyncio.Lock)
@router.get(
"/",
response_model=HealthResponse,
tags=["Health"],
summary="Health check endpoint",
)
async def health_check() -> HealthResponse:
settings = get_settings()
logger.info("Health check endpoint accessed")
handlers = {
"text": analyze_text,
"image": analyze_image,
}
models_status = {}
is_healthy = True
for content_type in settings.AVAILABLE_MODELS.keys():
handler = handlers.get(content_type)
if handler is not None and callable(handler):
models_status[content_type] = "ready"
else:
models_status[content_type] = "error_not_callable"
is_healthy = False
logger.error(f"Krytyczny brak! Handler dla typu '{content_type}' nie jest callable.")
overall_status = "ok" if is_healthy else "degraded"
return HealthResponse(
status=overall_status,
service="Deepfake Detection Service",
version=settings.APP_VERSION,
available_models=settings.AVAILABLE_MODELS,
supported_types=list(settings.AVAILABLE_MODELS.keys()),
models_status=models_status,
)
@router.post("/guilds/{guild_id}/setup", tags=["Setup"], dependencies=[Depends(verify_api_key)])
async def save_discord_guild_setup(guild_id: str, payload: GuildConfigSchema):
# Walidacja modeli z pliku ustawień
settings = get_settings()
allowed_text_models = settings.AVAILABLE_MODELS.get("text", [])
allowed_image_models = settings.AVAILABLE_MODELS.get("image", [])
# Walidujemy tylko wtedy, gdy model nie jest ustawiony na "none"
if payload.active_text_model and payload.active_text_model.lower() != "none":
if payload.active_text_model not in allowed_text_models:
raise HTTPException(
status_code=400,
detail=f"Model '{payload.active_text_model}' nie jest dozwolony. Wybierz z: {allowed_text_models}"
)
if payload.active_image_model and payload.active_image_model.lower() != "none":
if payload.active_image_model not in allowed_image_models:
raise HTTPException(
status_code=400,
detail=f"Model '{payload.active_image_model}' nie jest dozwolony. Wybierz z: {allowed_image_models}"
)
# Zapis konfiguracji przez config_manager
config_dict = payload.dict()
save_guild_config(guild_id, config_dict)
logger.info(f"Zapisano nową konfigurację dla serwera Discord {guild_id}")
return {
"status": "success",
"message": f"Konfiguracja dla serwera {guild_id} została zapisana.",
"config": config_dict,
}
@router.get("/guilds/{guild_id}/config", tags=["Setup"], dependencies=[Depends(verify_api_key)])
async def get_discord_guild_config(guild_id: str):
"""Zwraca zapisaną konfigurację dla konkretnego serwera Discord."""
configs = _load_all_configs()
guild_config = configs.get(guild_id, {})
return {
"active_text_model": guild_config.get("active_text_model", "none"),
"active_image_model": guild_config.get("active_image_model", "none"),
"log_channel_id": guild_config.get("log_channel_id", None),
"multi_model_workflow": guild_config.get("multi_model_workflow", False)
}
async def _execute_analysis(payload: AnalysisRequest, guild_id: str, settings) -> dict:
"""Funkcja pomocnicza wykonująca właściwy proces pobierania i analizy."""
if isinstance(payload, TextAnalysisRequest):
content_type = "text"
elif isinstance(payload, ImageAnalysisRequest):
content_type = "image"
else:
raise HTTPException(
status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
detail="Unsupported file/content type."
)
try:
if content_type == "text":
if len(payload.text) > settings.MAX_CONTENT_SIZES["text"]:
raise ValueError(f"Text content exceeds maximum length.")
if len(payload.text) < 50:
raise ValueError("Text content must be at least 50 characters")
result = await analyze_text(payload.text, guild_id)
elif content_type == "image":
image_bytes = await download_file(str(payload.image_url))
if not image_bytes:
raise ValueError("Failed to download image")
if len(image_bytes) > settings.MAX_CONTENT_SIZES["image"]:
raise ValueError(f"Image size exceeds maximum.")
result = await analyze_image(image_bytes, guild_id)
result["content_type"] = content_type
return result
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except SetupRequiredError as e:
raise HTTPException(status_code=400, detail=str(e))
except DeepfakeDetectionError as e:
raise HTTPException(status_code=e.status_code, detail=e.message)
except Exception as e:
logger.error(f"Analysis error: {str(e)}", exc_info=True)
raise HTTPException(status_code=500, detail=f"Failed to analyze {content_type}")
@router.post(
"/analyze",
response_model=AnalysisResponse,
responses={
400: {"model": ErrorResponse, "description": "Bad request"},
408: {"model": ErrorResponse, "description": "Request timeout"},
415: {"model": ErrorResponse, "description": "Unsupported media type"},
429: {"model": ErrorResponse, "description": "Too many requests"},
500: {"model": ErrorResponse, "description": "Internal server error"},
},
tags=["Analysis"],
summary="Analyze content for deepfake detection",
dependencies=[Depends(verify_api_key)]
)
async def analyze(request: Request, payload: AnalysisRequest) -> AnalysisResponse:
guild_id = payload.guild_id
user_id = payload.user_id
limit_item = parse("1/5seconds")
if not limiter.limiter.hit(limit_item, f"analyze:user:{user_id}"):
logger.warning(f"Użytkownik {user_id} przekroczył limit zapytań (1/5s).")
raise HTTPException(
status_code=429,
detail="Przekroczyłeś limit zapytań. Możesz wykonać tylko 1 analizę na 5 sekund."
)
settings = get_settings()
queue_service = get_queue_service()
# 2. Sprawdzamy, czy Redis jest aktywny w pliku konfiguracyjnym oraz czy klient został zainicjalizowany
if settings.REDIS_ENABLED and queue_service.redis_client is not None:
logger.info(f"Używam rozproszonej blokady Redis dla użytkownika {user_id}")
# Tworzymy blokadę przy użyciu klienta z QueueService [1.2.6]
redis_lock = queue_service.redis_client.lock(
"global_analysis_queue_lock", timeout=60, blocking_timeout=120
)
try:
async with redis_lock:
analysis_result = await _execute_analysis(payload, guild_id, settings)
except LockError:
logger.error(f"Użytkownik {user_id} odrzucony z kolejki Redis z powodu timeoutu.")
raise HTTPException(
status_code=503,
detail="Serwer jest zbyt zajęty (kolejka Redis przepełniona). Spróbuj ponownie za chwilę."
)
else:
# FALLBACK: Jeśli Redis jest wyłączony, aplikacja automatycznie używa kolejki in-memory
logger.info(f"Redis jest wyłączony. Używam lokalnej blokady in-memory dla gildii {guild_id}")
local_lock = local_guild_locks[guild_id]
async with local_lock:
analysis_result = await _execute_analysis(payload, guild_id, settings)
# 3. Zwrócenie wyniku analizy
content_type = analysis_result["content_type"]
logger.info(f"{content_type.capitalize()} analysis completed. Result: {analysis_result}")
used_model = analysis_result.get("used_model", settings.AVAILABLE_MODELS.get(content_type)[0])
return AnalysisResponse(
is_deepfake=analysis_result["is_deepfake"],
confidence=analysis_result["confidence"],
analysis_time=analysis_result["analysis_time"],
used_model=used_model,
content_type=content_type,
details=analysis_result.get("details"),
)
from app.api.factcheck_router import router as factcheck_router
router.include_router(factcheck_router) #kupczak tu był
|