Spaces:
Building
Building
""" | |
Flare β Main Application (Refactored) | |
===================================== | |
""" | |
# FastAPI imports | |
from fastapi import FastAPI, WebSocket, Request, status | |
from fastapi.staticfiles import StaticFiles | |
from fastapi.responses import FileResponse, JSONResponse | |
from fastapi.middleware.cors import CORSMiddleware | |
# Standard library | |
import uvicorn | |
import os | |
from pathlib import Path | |
import mimetypes | |
import uuid | |
import traceback | |
from datetime import datetime | |
# Pydantic | |
from pydantic import ValidationError | |
# Project imports | |
from websocket_handler import websocket_endpoint | |
from chat_handler import router as chat_router | |
from admin_routes import router as admin_router, start_cleanup_task | |
from llm_startup import run_in_thread | |
from session import session_store, start_session_cleanup | |
from config_provider import ConfigProvider | |
# Logger imports (utils.log yerine) | |
from logger import log_error, log_info, log_warning | |
# Exception imports | |
from exceptions import ( | |
FlareException, RaceConditionError, format_error_response, | |
get_http_status_code | |
) | |
ALLOWED_ORIGINS = os.getenv("ALLOWED_ORIGINS", "http://localhost:4200").split(",") | |
# ===================== Environment Setup ===================== | |
def setup_environment(): | |
"""Setup environment based on deployment mode""" | |
cfg = ConfigProvider.get() | |
log_info("=" * 60) | |
log_info("π Flare Starting", version="2.0.0") | |
log_info(f"π LLM Provider: {cfg.global_config.llm_provider.name}") | |
log_info(f"π€ TTS Provider: {cfg.global_config.tts_provider.name}") | |
log_info(f"π§ STT Provider: {cfg.global_config.stt_provider.name}") | |
log_info("=" * 60) | |
if cfg.global_config.is_cloud_mode(): | |
log_info("βοΈ Cloud Mode: Using HuggingFace Secrets") | |
log_info("π Required secrets: JWT_SECRET, FLARE_TOKEN_KEY") | |
# Check for provider-specific tokens | |
llm_config = cfg.global_config.get_provider_config("llm", cfg.global_config.llm_provider.name) | |
if llm_config and llm_config.requires_repo_info: | |
log_info("π LLM requires SPARK_TOKEN for repository operations") | |
else: | |
log_info("π’ On-Premise Mode: Using .env file") | |
if not Path(".env").exists(): | |
log_warning("β οΈ WARNING: .env file not found!") | |
log_info("π Copy .env.example to .env and configure it") | |
# Run setup | |
setup_environment() | |
# Fix MIME types for JavaScript files | |
mimetypes.add_type("application/javascript", ".js") | |
mimetypes.add_type("text/css", ".css") | |
app = FastAPI( | |
title="Flare Orchestration Service", | |
version="2.0.0", | |
description="LLM-driven intent & API flow engine with multi-provider support", | |
) | |
# CORS for development | |
if os.getenv("ENVIRONMENT", "development") == "development": | |
app.add_middleware( | |
CORSMiddleware, | |
allow_origins=ALLOWED_ORIGINS, | |
allow_credentials=True, | |
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], | |
allow_headers=["*"], | |
max_age=3600, | |
expose_headers=["X-Request-ID"] | |
) | |
log_info(f"π§ CORS enabled for origins: {ALLOWED_ORIGINS}") | |
# Request ID middleware | |
async def add_request_id(request: Request, call_next): | |
"""Add request ID for tracking""" | |
request_id = str(uuid.uuid4()) | |
request.state.request_id = request_id | |
# Log request start | |
log_info( | |
"Request started", | |
request_id=request_id, | |
method=request.method, | |
path=request.url.path, | |
client=request.client.host if request.client else "unknown" | |
) | |
try: | |
response = await call_next(request) | |
# Add request ID to response headers | |
response.headers["X-Request-ID"] = request_id | |
# Log request completion | |
log_info( | |
"Request completed", | |
request_id=request_id, | |
status_code=response.status_code, | |
method=request.method, | |
path=request.url.path | |
) | |
return response | |
except Exception as e: | |
log_error( | |
"Request failed", | |
request_id=request_id, | |
error=str(e), | |
traceback=traceback.format_exc() | |
) | |
raise | |
run_in_thread() # Start LLM startup notifier if needed | |
start_cleanup_task() # Activity log cleanup | |
start_session_cleanup() # Session cleanup | |
# ---------------- Core chat/session routes -------------------------- | |
app.include_router(chat_router, prefix="/api") | |
# ---------------- Admin API routes ---------------------------------- | |
app.include_router(admin_router) | |
# Global exception handler | |
async def global_exception_handler(request: Request, exc: Exception): | |
"""Handle all unhandled exceptions""" | |
request_id = getattr(request.state, 'request_id', 'unknown') | |
# Log the full exception | |
log_error( | |
"Unhandled exception", | |
request_id=request_id, | |
endpoint=str(request.url), | |
method=request.method, | |
error=str(exc), | |
error_type=type(exc).__name__, | |
traceback=traceback.format_exc() | |
) | |
# Special handling for FlareExceptions | |
if isinstance(exc, FlareException): | |
status_code = get_http_status_code(exc) | |
response_body = format_error_response(exc, request_id) | |
# Special message for race conditions | |
if isinstance(exc, RaceConditionError): | |
response_body["user_action"] = "Please reload the data and try again" | |
return JSONResponse( | |
status_code=status_code, | |
content=response_body | |
) | |
# Generic error response | |
return JSONResponse( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
content={ | |
"error": "InternalServerError", | |
"message": "An unexpected error occurred. Please try again later.", | |
"request_id": request_id, | |
"timestamp": datetime.utcnow().isoformat() | |
} | |
) | |
# Validation error handler | |
async def validation_exception_handler(request: Request, exc: ValidationError): | |
"""Handle Pydantic validation errors""" | |
request_id = getattr(request.state, 'request_id', 'unknown') | |
errors = [] | |
for error in exc.errors(): | |
field = " -> ".join(str(x) for x in error['loc']) | |
errors.append({ | |
'field': field, | |
'message': error['msg'], | |
'type': error['type'], | |
'input': error.get('input') | |
}) | |
log_warning( | |
"Validation error", | |
request_id=request_id, | |
errors=errors | |
) | |
return JSONResponse( | |
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, | |
content={ | |
"error": "ValidationError", | |
"message": "Invalid request data. Please check the fields and try again.", | |
"details": errors, | |
"request_id": request_id, | |
"timestamp": datetime.utcnow().isoformat() | |
} | |
) | |
# ---------------- Metrics endpoint ----------------- | |
async def get_metrics(): | |
"""Get system metrics""" | |
import psutil | |
import gc | |
# Memory info | |
process = psutil.Process() | |
memory_info = process.memory_info() | |
# Session stats | |
session_stats = session_store.get_session_stats() | |
metrics = { | |
"memory": { | |
"rss_mb": memory_info.rss / 1024 / 1024, | |
"vms_mb": memory_info.vms / 1024 / 1024, | |
"percent": process.memory_percent() | |
}, | |
"cpu": { | |
"percent": process.cpu_percent(interval=0.1), | |
"num_threads": process.num_threads() | |
}, | |
"sessions": session_stats, | |
"gc": { | |
"collections": gc.get_count(), | |
"objects": len(gc.get_objects()) | |
}, | |
"uptime_seconds": time.time() - process.create_time() | |
} | |
return metrics | |
# ---------------- Health probe (HF Spaces watchdog) ----------------- | |
def health_check(): | |
"""Health check with detailed status""" | |
return { | |
"status": "ok", | |
"version": "2.0.0", | |
"timestamp": datetime.utcnow().isoformat(), | |
"environment": os.getenv("ENVIRONMENT", "development") | |
} | |
# ---------------- WebSocket route for real-time STT ------------------ | |
async def conversation_websocket(websocket: WebSocket, session_id: str): | |
await websocket_endpoint(websocket, session_id) | |
# ---------------- Serve Angular UI if exists ------------------------ | |
static_dir = Path(__file__).parent / "static" | |
if static_dir.exists(): | |
log_info("π¨ Serving Angular UI from /static directory") | |
# Mount static files with custom handler for Angular routing | |
async def serve_angular(path: str): | |
# API routes should not be handled here | |
if path.startswith("api/"): | |
return {"error": "Not found"}, 404 | |
# Try to serve the exact file first | |
file_path = static_dir / path | |
if file_path.is_file(): | |
return FileResponse(file_path) | |
# For Angular routes, always serve index.html | |
index_path = static_dir / "index.html" | |
if index_path.exists(): | |
return FileResponse(index_path) | |
return {"error": "Not found"}, 404 | |
# Mount static files for assets | |
app.mount("/", StaticFiles(directory=str(static_dir), html=True), name="static") | |
else: | |
log_info("β οΈ No UI found. Run 'cd flare-ui && npm run build' to build the UI.") | |
if __name__ == "__main__": | |
log_info("π Starting Flare backend on port 7860...") | |
uvicorn.run(app, host="0.0.0.0", port=7860) |