Spaces:
Paused
Paused
| # main.py | |
| import os | |
| import uuid | |
| import logging | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import FileResponse, Response | |
| from fastapi.staticfiles import StaticFiles | |
| from pydantic import BaseModel | |
| import requests | |
| from typing import Optional, Any, Dict | |
| # ------------------------------- | |
| # CONFIGURACIÓN DE LOGGING | |
| # ------------------------------- | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' | |
| ) | |
| logger = logging.getLogger(__name__) | |
| # ------------------------------- | |
| # CARGA DE SECRETS | |
| # ------------------------------- | |
| FLOW_API_URL = os.getenv("FLOW_API_URL") | |
| API_KEY = os.getenv("LANGFLOW_API_KEY") | |
| if not FLOW_API_URL: | |
| raise RuntimeError("❌ FLOW_API_URL no está definido. Agrégalo en los Secrets de Hugging Face.") | |
| if not API_KEY: | |
| raise RuntimeError("❌ LANGFLOW_API_KEY no está definido. Agrégalo en los Secrets de Hugging Face.") | |
| logger.info(f"✅ FLOW_API_URL configurado: {FLOW_API_URL[:30]}...") | |
| logger.info(f"✅ LANGFLOW_API_KEY cargada (longitud {len(API_KEY)})") | |
| # ------------------------------- | |
| # INICIALIZACIÓN DE LA APP | |
| # ------------------------------- | |
| app = FastAPI() | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| app.mount("/static", StaticFiles(directory="static"), name="static") | |
| async def serve_index(): | |
| return FileResponse("static/index.html") | |
| async def serve_logo(): | |
| logo_path = "static/te.png" | |
| if os.path.exists(logo_path): | |
| return FileResponse(logo_path) | |
| svg = '''<svg width="40" height="40" ...>...</svg>''' # placeholder SVG | |
| return Response(svg, media_type="image/svg+xml", headers={"Cache-Control":"public, max-age=3600"}) | |
| # ------------------------------- | |
| # MODELOS DE Pydantic | |
| # ------------------------------- | |
| class AnalyzeRequest(BaseModel): | |
| url: str | |
| class AnalyzeResponse(BaseModel): | |
| result: str | |
| success: bool = True | |
| error: Optional[str] = None | |
| # ------------------------------- | |
| # HELPER DE EXTRACCIÓN | |
| # ------------------------------- | |
| def _extract_text_from_response(data: Any) -> Optional[str]: | |
| if isinstance(data, str): | |
| return data | |
| if isinstance(data, dict): | |
| for key in ("outputs","result","message","text","content"): | |
| val = data.get(key) | |
| if isinstance(val, str): | |
| return val | |
| elif val is not None: | |
| txt = _extract_text_from_response(val) | |
| if txt: | |
| return txt | |
| for val in data.values(): | |
| txt = _extract_text_from_response(val) | |
| if txt: | |
| return txt | |
| if isinstance(data, list): | |
| for item in data: | |
| txt = _extract_text_from_response(item) | |
| if txt: | |
| return txt | |
| return None | |
| # ------------------------------- | |
| # ENDPOINT /analyze | |
| # ------------------------------- | |
| async def analyze(request: AnalyzeRequest): | |
| logger.info(f"📥 Recibida solicitud de análisis para URL: {request.url}") | |
| session_id = str(uuid.uuid4()) | |
| payload = { | |
| "input_value": request.url, | |
| "input_type": "chat", | |
| "output_type": "chat", | |
| "session_id": session_id, | |
| "output_component": "", | |
| "tweaks": None | |
| } | |
| headers = { | |
| "Content-Type": "application/json", | |
| "User-Agent": "TrueEye-HuggingFace-Space/1.0", | |
| "x-api-key": API_KEY | |
| } | |
| try: | |
| logger.info("📤 Enviando petición a Langflow...") | |
| logger.debug(f"Payload: {payload}") | |
| resp = requests.post(FLOW_API_URL, json=payload, headers=headers, timeout=300) | |
| logger.info(f"📨 Respuesta recibida. Status: {resp.status_code}") | |
| resp.raise_for_status() | |
| data = resp.json() | |
| logger.debug(f"Respuesta JSON completa: {data}") | |
| # Extraer texto final | |
| result_text = _extract_text_from_response(data) | |
| if not result_text: | |
| logger.warning("⚠️ No se pudo extraer texto de la respuesta") | |
| result_text = "⚠️ Se procesó la solicitud pero no se pudo extraer el resultado." | |
| logger.info("✅ Análisis completado exitosamente") | |
| return AnalyzeResponse(result=result_text) | |
| except requests.exceptions.Timeout: | |
| logger.error("⏱️ Timeout en la petición a Langflow") | |
| return AnalyzeResponse(result="❌ Error: Timeout (el análisis tardó demasiado)", success=False, error="timeout") | |
| except requests.exceptions.HTTPError as e: | |
| body = e.response.text if e.response is not None else "<no body>" | |
| logger.error(f"🚫 Error HTTP {e.response.status_code if e.response else ''}: {body}") | |
| return AnalyzeResponse( | |
| result=f"❌ Error HTTP al llamar al Flow: {body}", | |
| success=False, | |
| error=f"http_{e.response.status_code if e.response else 'unknown'}" | |
| ) | |
| except Exception as e: | |
| logger.exception("💥 Error inesperado en /analyze") | |
| return AnalyzeResponse(result=f"❌ Error inesperado: {e}", success=False, error="unknown") | |
| # ------------------------------- | |
| # HEALTHCHECK | |
| # ------------------------------- | |
| async def health_check(): | |
| return { | |
| "status": "healthy", | |
| "flow_url": bool(FLOW_API_URL), | |
| "service": "TrueEye Reports" | |
| } |