Spaces:
Running
Running
| import os | |
| import sys | |
| import time | |
| import asyncio | |
| # Force UTF-8 for Windows console emoji printing | |
| if sys.stdout.encoding != 'utf-8': | |
| try: | |
| sys.stdout.reconfigure(encoding='utf-8') | |
| except Exception: | |
| pass | |
| os.environ["USE_TF"] = "0" | |
| os.environ["USE_TORCH"] = "1" | |
| os.environ["TF_USE_LEGACY_KERAS"] = "1" | |
| import uvicorn | |
| import logging | |
| from fastapi import FastAPI, Request | |
| from fastapi.staticfiles import StaticFiles | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.middleware.trustedhost import TrustedHostMiddleware | |
| from starlette.middleware.base import BaseHTTPMiddleware | |
| from dotenv import load_dotenv | |
| from pathlib import Path | |
| from starlette.middleware.sessions import SessionMiddleware | |
| # Configure Logging | |
| # Configure Logging | |
| # Note: Basic logging is handled in settings.py which is imported below. | |
| # We just ensure uvicorn loggers verify settings. | |
| import sys | |
| logging.getLogger("uvicorn.access").handlers = [logging.StreamHandler(sys.stdout)] | |
| logging.getLogger("uvicorn.error").handlers = [logging.StreamHandler(sys.stdout)] | |
| logger = logging.getLogger("main") | |
| # Load env using the new Settings logic (it loads env automatically on import) | |
| from config.settings import Settings | |
| # Import Routers | |
| from api.v1.endpoints import ( | |
| chat, | |
| files, | |
| analytics, | |
| reports, | |
| email_prefs, | |
| decisions, | |
| synthetic, | |
| anomalies, | |
| lineage, | |
| collaboration, | |
| simulator, | |
| search, | |
| voice, | |
| ws, | |
| live_streaming | |
| ) | |
| # Import Autonomous Dashboard API | |
| from api.v1.endpoints import dashboard_api | |
| # ๐ง Import Autonomous Brain API | |
| from api.v1.endpoints import brain | |
| logger.info("โ Autonomous Brain API loaded") | |
| # Initialize App | |
| app = FastAPI( | |
| title="DataVision - Autonomous AI Data Platform", | |
| version="3.0.0", | |
| docs_url="/docs", | |
| redoc_url="/redoc", | |
| openapi_url="/openapi.json", | |
| ) | |
| async def verify_tables(): | |
| from database.db import engine | |
| from database import orm | |
| from database.orm import Base | |
| from sqlalchemy import text | |
| try: | |
| async with engine.begin() as conn: | |
| logger.info("๐ฆ Verifying database tables exist (auto-healing)...") | |
| await conn.run_sync(Base.metadata.create_all) | |
| await conn.execute(text("ALTER TABLE IF EXISTS api_keys ADD COLUMN IF NOT EXISTS api_key VARCHAR(255) NOT NULL DEFAULT ''")) | |
| await conn.execute(text("ALTER TABLE IF EXISTS api_keys ADD COLUMN IF NOT EXISTS status VARCHAR(20) NOT NULL DEFAULT 'active'")) | |
| await conn.execute(text("ALTER TABLE IF EXISTS api_keys ADD COLUMN IF NOT EXISTS data_processed_mb DOUBLE PRECISION NOT NULL DEFAULT 0")) | |
| await conn.execute(text("ALTER TABLE IF EXISTS chat_channels ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()")) | |
| await conn.execute(text("ALTER TABLE IF EXISTS channel_messages ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()")) | |
| await conn.execute(text("ALTER TABLE IF EXISTS message_reactions ADD COLUMN IF NOT EXISTS created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()")) | |
| await conn.execute(text("ALTER TABLE IF EXISTS message_reactions ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()")) | |
| await conn.execute(text("ALTER TABLE IF EXISTS api_call_logs ADD COLUMN IF NOT EXISTS http_method VARCHAR(10) NOT NULL DEFAULT 'GET'")) | |
| await conn.execute(text("ALTER TABLE IF EXISTS api_call_logs ADD COLUMN IF NOT EXISTS response_time_ms INTEGER NOT NULL DEFAULT 0")) | |
| await conn.execute(text("""DO $$ BEGIN | |
| IF to_regclass('public.webhook_endpoints') IS NOT NULL | |
| AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'webhook_endpoints' AND column_name = 'secret') | |
| AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'webhook_endpoints' AND column_name = 'secret_key') THEN | |
| ALTER TABLE webhook_endpoints RENAME COLUMN secret TO secret_key; | |
| END IF; | |
| END $$;""")) | |
| logger.info("โ Database tables verified/created successfully!") | |
| except Exception as e: | |
| logger.error(f"โ ๏ธ Failed to auto-heal database tables: {e}") | |
| # ============================================ | |
| # SECURITY MIDDLEWARE | |
| # ============================================ | |
| class SecurityHeadersMiddleware(BaseHTTPMiddleware): | |
| """Add security headers to all responses""" | |
| async def dispatch(self, request: Request, call_next): | |
| response = await call_next(request) | |
| # Security headers | |
| response.headers["X-Content-Type-Options"] = "nosniff" | |
| # Allow embedding in HuggingFace iframe | |
| response.headers["X-Frame-Options"] = "ALLOWALL" | |
| response.headers["X-XSS-Protection"] = "1; mode=block" | |
| response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin" | |
| # Remove server header (MutableHeaders uses del, not pop) | |
| if "server" in response.headers: | |
| del response.headers["server"] | |
| return response | |
| # Add security headers middleware | |
| app.add_middleware(SecurityHeadersMiddleware) | |
| # Add session middleware (required for Authlib OAuth) | |
| # For Hugging Face Spaces (which uses an iframe), same_site must be "none" and https_only=True to prevent dropping the session cookie | |
| session_secret = os.environ.get("JWT_SECRET") or os.environ.get("JWT_SECRET_KEY") or "datavision-production-jwt-secret-key-32bytes-long!" | |
| is_prod = bool(os.environ.get("SPACE_HOST")) | |
| app.add_middleware( | |
| SessionMiddleware, | |
| secret_key=session_secret, | |
| same_site="none" if is_prod else "lax", | |
| https_only=is_prod | |
| ) | |
| class APICallLogMiddleware(BaseHTTPMiddleware): | |
| """Log all API requests to the APICallLog table""" | |
| async def dispatch(self, request: Request, call_next): | |
| start_time = time.time() | |
| response = await call_next(request) | |
| process_time_ms = int((time.time() - start_time) * 1000) | |
| # Log all /api/ requests, ignoring /api/config and static files | |
| if request.url.path.startswith("/api/") and not request.url.path.startswith("/api/config"): | |
| asyncio.create_task(self.log_request( | |
| endpoint=request.url.path, | |
| method=request.method, | |
| status_code=response.status_code, | |
| latency_ms=process_time_ms, | |
| user_id=request.headers.get("X-User-ID") | |
| )) | |
| return response | |
| async def log_request(self, endpoint: str, method: str, status_code: int, latency_ms: int, user_id: str): | |
| try: | |
| from database.db import AsyncSessionLocal | |
| from database.orm import APICallLog | |
| import uuid | |
| parsed_uid = None | |
| if user_id: | |
| try: | |
| parsed_uid = uuid.UUID(user_id) | |
| except ValueError: | |
| parsed_uid = None | |
| async with AsyncSessionLocal() as db: | |
| log = APICallLog( | |
| user_id=parsed_uid, | |
| endpoint=endpoint, | |
| http_method=method, | |
| status_code=status_code, | |
| response_time_ms=latency_ms | |
| ) | |
| db.add(log) | |
| await db.commit() | |
| except Exception as e: | |
| logger.error(f"Failed to log API call: {e}") | |
| app.add_middleware(APICallLogMiddleware) | |
| # Rate Limit Header Middleware | |
| from core.rate_limiter import RateLimitHeaderMiddleware, get_rate_limiter | |
| app.add_middleware(RateLimitHeaderMiddleware) | |
| logger.info(f"โ Rate limiter: {type(get_rate_limiter()).__name__}") | |
| # Trusted host middleware (prevent host header injection) | |
| # ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "localhost,127.0.0.1").split(",") | |
| # if os.environ.get("ENVIRONMENT") == "production": | |
| # app.add_middleware(TrustedHostMiddleware, allowed_hosts=ALLOWED_HOSTS) | |
| # CORS - Secure configuration | |
| # Allow HuggingFace Spaces domains and local development | |
| ALLOWED_ORIGINS = os.environ.get( | |
| "CORS_ORIGINS", | |
| "http://localhost:5173,http://localhost:5174,http://localhost:3000,http://localhost:8000,https://huggingface.co,https://killerkumar-ai-business-analyst.hf.space,https://datavision-ai-datavision.hf.space,https://*.hf.space" | |
| ).split(",") | |
| # For HuggingFace Spaces, we need to allow all origins for the embedded iframe | |
| if os.path.exists("/app"): # Running in Docker/HF | |
| ALLOWED_ORIGINS = ["*"] | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=ALLOWED_ORIGINS, | |
| allow_credentials=True, | |
| allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], | |
| allow_headers=["*"], # Allow all headers for HF compatibility | |
| max_age=600, | |
| ) | |
| # Mount Static Files | |
| static_dir = os.path.join(os.path.dirname(__file__), "static") | |
| os.makedirs(static_dir, exist_ok=True) | |
| app.mount("/static", StaticFiles(directory=static_dir), name="static") | |
| app.include_router(chat.router, prefix="/api/v1/chat", tags=["Chat"]) | |
| app.include_router(files.router, prefix="/api/v1/files", tags=["Files"]) | |
| app.include_router(analytics.router, prefix="/api/v1/analytics", tags=["Analytics"]) | |
| app.include_router(reports.router, prefix="/api/v1/reports", tags=["Reports"]) | |
| app.include_router(email_prefs.router, prefix="/api/v1/settings", tags=["Settings"]) | |
| app.include_router(decisions.router, prefix="/api/v1/decisions", tags=["Decisions"]) | |
| app.include_router(synthetic.router, prefix="/api/v1/synthetic", tags=["Synthetic Data"]) | |
| # Mount new feature APIs | |
| app.include_router(anomalies.router, prefix="/api/v1", tags=["Anomalies"]) | |
| app.include_router(lineage.router, prefix="/api/v1/lineage", tags=["Lineage"]) | |
| app.include_router(collaboration.router, prefix="/api/v1/collaboration", tags=["Collaboration"]) | |
| app.include_router(simulator.router, prefix="/api/v1", tags=["Simulator"]) | |
| app.include_router(search.router, prefix="/api/v1", tags=["Search"]) | |
| app.include_router(voice.router, prefix="/api/v1/voice", tags=["Voice"]) | |
| app.include_router(ws.router, prefix="/api/v1/ws", tags=["WebSockets"]) | |
| app.include_router(live_streaming.router, prefix="/api/v1", tags=["Live Streaming"]) | |
| from api.v1.endpoints import auth, admin | |
| app.include_router(auth.router, prefix="/api/v1/auth", tags=["Auth"]) | |
| app.include_router(admin.router, prefix="/api/v1/admin", tags=["Admin"]) | |
| # ๐ค Exports API (PDF, PPTX, Email) | |
| from api.v1.endpoints import exports | |
| app.include_router(exports.router, prefix="/api/v1", tags=["Exports"]) | |
| logger.info("โ Exports API loaded (PDF, PPTX, Email)") | |
| # ๐ Autonomous Dashboard API | |
| app.include_router(dashboard_api.router, prefix="/api/v1/dashboard", tags=["Dashboard"]) | |
| # ๐ง Autonomous Brain API | |
| app.include_router(brain.router, prefix="/api/v1/brain", tags=["Brain"]) | |
| # ๐ DataVision API v2 (All Features) | |
| from api.v1.endpoints import datavision_api | |
| app.include_router(datavision_api.router, prefix="/api/v2", tags=["DataVision v2"]) | |
| logger.info("โ DataVision API v2 loaded") | |
| # ๐ค AutoML API (Production ML) | |
| from api.v1.endpoints import automl_api | |
| app.include_router(automl_api.router, prefix="/api/v2/automl", tags=["AutoML"]) | |
| app.include_router(automl_api.router, prefix="/api/v1/automl", tags=["AutoML v1"]) # Also register at v1 for compatibility | |
| logger.info("โ AutoML API loaded") | |
| # ๐ค Autonomous API (Model Management + Auto-Fix) | |
| from api.v1.endpoints import autonomous_api | |
| app.include_router(autonomous_api.router, prefix="/api/v2/autonomous", tags=["Autonomous"]) | |
| logger.info("โ Autonomous API loaded (Model Management + Auto-Fix)") | |
| # ๐ค Agentic AutoML (Multi-Agent ML Pipeline) | |
| from api.v1.endpoints import agentic_automl_api | |
| app.include_router(agentic_automl_api.router, prefix="/api/v2", tags=["Agentic AutoML"]) | |
| logger.info("โ Agentic AutoML API loaded (9 Specialized Agents)") | |
| # ๐ฅ Data Health API | |
| from api.v1.endpoints import data_health_api | |
| app.include_router(data_health_api.router, prefix="/api/v1", tags=["Data Health"]) | |
| logger.info("โ Data Health API loaded") | |
| # ๐ฎ Playground API | |
| from api.v1.endpoints import playground_api | |
| app.include_router(playground_api.router, prefix="/api/v1", tags=["Playground"]) | |
| logger.info("โ Playground API loaded") | |
| # ๐ Explainability API | |
| from api.v1.endpoints import explainability_api | |
| app.include_router(explainability_api.router, prefix="/api/v1", tags=["Explainability"]) | |
| logger.info("โ Explainability API loaded") | |
| # ๐ข Enterprise MLOps Deployment API | |
| from api.v1.endpoints import mlops | |
| app.include_router(mlops.router, prefix="/api/v1", tags=["MLOps"]) | |
| logger.info("โ MLOps API loaded") | |
| # ๐ฏ Clustering API (Unsupervised Learning) | |
| from api.v1.endpoints import clustering_api | |
| app.include_router(clustering_api.router, prefix="/api/v1/ml", tags=["Clustering"]) | |
| logger.info("โ Clustering API loaded (K-Means, DBSCAN, GMM, Spectral)") | |
| # ๐ป Web IDE API (Execution & Chat) | |
| from api.v1.endpoints import ide_api | |
| app.include_router(ide_api.router, prefix="/api/v1/ide", tags=["Web IDE"]) | |
| logger.info("โ Web IDE API loaded (Code Execution)") | |
| # ๐ง Agentic Autopilot API (V5 โ Autonomous Data Science) | |
| # โ๏ธ Autopilot & Agents | |
| from api.v1.endpoints import autopilot_api | |
| app.include_router(autopilot_api.router, prefix="/api/v1/autopilot", tags=["Autopilot"]) | |
| # ๐ Developer & API Center | |
| from api.v1.endpoints import developer | |
| app.include_router(developer.router, prefix="/api/v1/developer", tags=["Developer"]) | |
| # ๐ Model Deploy API | |
| from api.v1.endpoints import deploy | |
| app.include_router(deploy.router, prefix="/api/v1", tags=["Deploy"]) | |
| # ๐จ Anomaly Detection | |
| from api.v1.endpoints import anomalies | |
| app.include_router(anomalies.router, prefix="/api/v1/anomalies", tags=["Anomalies"]) | |
| # ๐๏ธ Computer Vision | |
| from api.v1.endpoints import cv_router | |
| app.include_router(cv_router.router, prefix="/api/v1/cv", tags=["Computer Vision"]) | |
| # ๐ง Vector AI & RAG Inspector | |
| from api.v1.endpoints import vector_router | |
| app.include_router(vector_router.router, prefix="/api/v1/vector", tags=["Vector AI"]) | |
| # ========================================== | |
| # ๐ V2 APIs (Agentic + RAG) | |
| # ========================================== | |
| from fastapi.responses import FileResponse | |
| # Frontend static directory (pre-built React app) | |
| frontend_static_dir = "/app/static" if os.path.exists("/app/static") else os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "frontend", "dist") | |
| async def get_frontend_config(): | |
| """ | |
| Secure config endpoint for frontend. | |
| Returns only PUBLIC configuration needed for client-side operations. | |
| """ | |
| return { | |
| "VITE_API_URL": os.environ.get("VITE_API_URL", ""), | |
| "auth_mode": "jwt" | |
| } | |
| async def api_info(): | |
| """API information endpoint""" | |
| return { | |
| "platform": "DataVision - Universal AI Data Platform", | |
| "version": "2.0.0", | |
| "status": "active", | |
| "apis": { | |
| "v1": "/api/v1/* (legacy endpoints)", | |
| "v2": "/api/v2/* (all features)" | |
| }, | |
| "capabilities": [ | |
| "autonomous_brain", | |
| "universal_agent", | |
| "chain_of_thought_reasoning", | |
| "react_framework", | |
| "knowledge_graphs", | |
| "predictive_intelligence", | |
| "root_cause_analysis", | |
| "ai_segmentation", | |
| "trend_detection", | |
| "cohort_analysis", | |
| "automl", | |
| "what_if_simulation", | |
| "enterprise_exports" | |
| ] | |
| } | |
| # Serve React frontend - must be after all API routes | |
| async def serve_frontend(): | |
| """Serve React frontend""" | |
| index_path = os.path.join(frontend_static_dir, "index.html") | |
| if os.path.exists(index_path): | |
| return FileResponse(index_path) | |
| return {"error": "Frontend not found", "path": index_path} | |
| # Serve logo files | |
| async def serve_logo_svg(): | |
| logo_path = os.path.join(frontend_static_dir, "logo.svg") | |
| if os.path.exists(logo_path): | |
| return FileResponse(logo_path, media_type="image/svg+xml") | |
| return {"error": "logo.svg not found"} | |
| async def serve_logo_png(): | |
| logo_path = os.path.join(frontend_static_dir, "logo.png") | |
| if os.path.exists(logo_path): | |
| return FileResponse(logo_path) | |
| return {"error": "logo.png not found"} | |
| async def serve_logo_jpg(): | |
| logo_path = os.path.join(frontend_static_dir, "logo.jpg") | |
| if os.path.exists(logo_path): | |
| return FileResponse(logo_path) | |
| return {"error": "logo.jpg not found"} | |
| async def serve_datavision_logo(): | |
| logo_path = os.path.join(frontend_static_dir, "datavision-logo.jpg") | |
| if os.path.exists(logo_path): | |
| return FileResponse(logo_path, media_type="image/jpeg") | |
| return {"error": "datavision-logo.jpg not found"} | |
| async def serve_datavision_logo_dark(): | |
| logo_path = os.path.join(frontend_static_dir, "datavision-logo-dark.jpg") | |
| if os.path.exists(logo_path): | |
| return FileResponse(logo_path, media_type="image/jpeg") | |
| return {"error": "datavision-logo-dark.jpg not found"} | |
| async def serve_datavision_logo_light(): | |
| logo_path = os.path.join(frontend_static_dir, "datavision-logo-light.jpg") | |
| if os.path.exists(logo_path): | |
| return FileResponse(logo_path, media_type="image/jpeg") | |
| return {"error": "datavision-logo-light.jpg not found"} | |
| async def serve_logo_transparent(): | |
| logo_path = os.path.join(frontend_static_dir, "logo_transparent.png") | |
| if os.path.exists(logo_path): | |
| return FileResponse(logo_path) | |
| return {"error": "logo_transparent.png not found"} | |
| async def serve_datavision_icon(): | |
| icon_path = os.path.join(frontend_static_dir, "datavision_icon_v3.png") | |
| if os.path.exists(icon_path): | |
| return FileResponse(icon_path) | |
| return {"error": "datavision_icon_v3.png not found"} | |
| # Serve service worker | |
| async def serve_sw(): | |
| return FileResponse(os.path.join(frontend_static_dir, "sw.js")) | |
| # Mount static assets (JS, CSS, images) | |
| if os.path.exists(frontend_static_dir): | |
| assets_dir = os.path.join(frontend_static_dir, "assets") | |
| if os.path.exists(assets_dir): | |
| app.mount("/assets", StaticFiles(directory=assets_dir), name="frontend-assets") | |
| frontend_public_dir = os.path.join(os.path.dirname(__file__), "..", "frontend", "public") | |
| # Catch-all route for SPA (React Router) & static files - must be LAST | |
| async def spa_fallback(full_path: str): | |
| """Fallback for client-side routing and static images""" | |
| # Don't catch API routes or FastAPI built-in docs | |
| if full_path.startswith("api/") or full_path.startswith("docs") or full_path.startswith("redoc") or full_path.startswith("openapi.json"): | |
| return {"error": "Not found"} | |
| # 1. Check if direct file exists in frontend static dir (dist) | |
| dist_file = os.path.join(frontend_static_dir, full_path) | |
| if os.path.isfile(dist_file): | |
| return FileResponse(dist_file) | |
| # 2. Check if file exists in frontend public dir | |
| public_file = os.path.join(frontend_public_dir, full_path) | |
| if os.path.isfile(public_file): | |
| return FileResponse(public_file) | |
| # 3. Otherwise serve index.html for SPA client-side routing | |
| index_path = os.path.join(frontend_static_dir, "index.html") | |
| if os.path.exists(index_path): | |
| return FileResponse(index_path) | |
| return {"error": "Frontend not found"} | |
| async def startup_event(): | |
| logger.info("=" * 50) | |
| logger.info("๐ DATAVISION 2.0 - Universal AI Data Platform") | |
| logger.info("=" * 50) | |
| logger.info("๐ง Autonomous Brain: ACTIVE") | |
| logger.info("๐ค Universal Agent: ACTIVE") | |
| logger.info("๐ Knowledge Graphs: ACTIVE") | |
| logger.info("๐ฎ Predictive Intelligence: ACTIVE") | |
| logger.info("โก Advanced MCPs: LOADED") | |
| logger.info("๐ข Enterprise Features: ENABLED") | |
| # ๐ง Start Email Scheduler for Daily/Weekly Reports | |
| try: | |
| from apscheduler.schedulers.asyncio import AsyncIOScheduler | |
| from apscheduler.triggers.cron import CronTrigger | |
| from scheduler.scheduled_reporter import check_and_send_reports | |
| scheduler = AsyncIOScheduler() | |
| # Run every minute to check for scheduled email times | |
| scheduler.add_job( | |
| check_and_send_reports, | |
| CronTrigger(minute="*"), # Every minute | |
| id="email_report_checker", | |
| name="Check and send scheduled email reports", | |
| replace_existing=True | |
| ) | |
| scheduler.start() | |
| logger.info("๐ง Email Scheduler: ACTIVE (checking every minute)") | |
| except Exception as e: | |
| logger.warning(f"โ ๏ธ Email scheduler not started: {e}") | |
| # ๐ Start WebSocket real-time updates task | |
| try: | |
| from api.v1.endpoints.ws import push_realtime_updates | |
| import asyncio | |
| asyncio.create_task(push_realtime_updates()) | |
| logger.info("๐ WebSocket real-time updates: ACTIVE") | |
| except Exception as e: | |
| logger.warning(f"โ ๏ธ WebSocket task not started: {e}") | |
| logger.info("=" * 50) | |
| logger.info("๐ก API v2 available at: /api/v2") | |
| logger.info("๐ Docs at: /docs") | |
| logger.info("=" * 50) | |
| async def shutdown_event(): | |
| logger.info("๐ DataVision Shutting Down...") | |
| if __name__ == "__main__": | |
| import os | |
| port = int(os.environ.get("PORT", 8000)) | |
| uvicorn.run("main:app", host="0.0.0.0", port=port, reload=False) | |