Spaces:
Paused
Paused
| import gradio as gr | |
| import threading | |
| import uvicorn | |
| import os | |
| import sys | |
| import uuid | |
| import json | |
| import time | |
| from datetime import datetime | |
| from fastapi import FastAPI, Request | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import JSONResponse | |
| import requests | |
| print("=" * 60) | |
| print("π STARTING STATUS TRACKER") | |
| print("=" * 60) | |
| print(f"Python version: {sys.version}") | |
| print(f"Gradio version: {gr.__version__}") | |
| # ============================================= | |
| # CREATE FASTAPI APP | |
| # ============================================= | |
| app = FastAPI(title="Status Tracker API") | |
| # Add CORS middleware - CRITICAL for n8n access | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # ============================================= | |
| # IN-MEMORY STORAGE | |
| # ============================================= | |
| projects = {} | |
| # ============================================= | |
| # API ENDPOINTS | |
| # ============================================= | |
| async def root(): | |
| """Root endpoint with API documentation""" | |
| return { | |
| "name": "Status Tracker API", | |
| "version": "1.0.0", | |
| "status": "running", | |
| "active_projects": len(projects), | |
| "endpoints": { | |
| "health": "GET /health", | |
| "test": "GET /test", | |
| "register": "POST /register", | |
| "update": "POST /update", | |
| "status": "GET /status/{project_id}" | |
| }, | |
| "timestamp": datetime.now().isoformat() | |
| } | |
| async def health(): | |
| """Health check endpoint""" | |
| return { | |
| "status": "healthy", | |
| "active_projects": len(projects), | |
| "timestamp": datetime.now().isoformat() | |
| } | |
| async def test(): | |
| """Simple test endpoint to verify API is working""" | |
| return { | |
| "message": "API is working!", | |
| "timestamp": datetime.now().isoformat() | |
| } | |
| async def register_project(request: Request): | |
| """Register a new project with the tracker""" | |
| try: | |
| data = await request.json() | |
| project_id = data.get("project_id") or str(uuid.uuid4())[:8] | |
| print(f"\nπ Registering project: {project_id}") | |
| print(f" Webhook: {data.get('n8n_webhook')}") | |
| print(f" Services: {data.get('services', [])}") | |
| print(f" Chat ID: {data.get('chat_id')}") # ADDED: Debug print | |
| projects[project_id] = { | |
| "project_id": project_id, | |
| "n8n_webhook": data.get("n8n_webhook"), | |
| "chat_id": data.get("chat_id"), # ADDED: Store chat_id | |
| "services": {service: {"status": "pending", "file_urls": []} | |
| for service in data.get("services", [])}, | |
| "created_at": datetime.now().isoformat(), | |
| "completed_at": None, | |
| "last_update": datetime.now().isoformat() | |
| } | |
| return { | |
| "status": "registered", | |
| "project_id": project_id, | |
| "webhook": data.get("n8n_webhook") | |
| } | |
| except Exception as e: | |
| print(f"β Registration error: {e}") | |
| return JSONResponse(status_code=500, content={"error": str(e)}) | |
| async def update_status(request: Request): | |
| """Update status for a service""" | |
| try: | |
| data = await request.json() | |
| project_id = data.get("project_id") | |
| service_type = data.get("service_type") | |
| print(f"\nπ₯ Update for {project_id} - {service_type}: {data.get('status')}") | |
| if project_id not in projects: | |
| return JSONResponse(status_code=404, content={"error": "Project not found"}) | |
| project = projects[project_id] | |
| project["last_update"] = datetime.now().isoformat() | |
| # Update service status | |
| if service_type in project["services"]: | |
| project["services"][service_type] = { | |
| "status": data.get("status", "unknown"), | |
| "file_urls": data.get("file_urls", []), | |
| "updated_at": datetime.now().isoformat() | |
| } | |
| else: | |
| # Add new service if not in original list | |
| project["services"][service_type] = { | |
| "status": data.get("status", "unknown"), | |
| "file_urls": data.get("file_urls", []), | |
| "updated_at": datetime.now().isoformat() | |
| } | |
| # Check if all services are complete | |
| all_completed = all( | |
| s.get("status") == "completed" | |
| for s in project["services"].values() | |
| ) | |
| print(f" All completed: {all_completed}") | |
| if all_completed and not project.get("completed_at"): | |
| project["completed_at"] = datetime.now().isoformat() | |
| print(f"π Project {project_id} COMPLETED!") | |
| # Send webhook to n8n | |
| if project.get("n8n_webhook"): | |
| try: | |
| webhook_data = { | |
| "project_id": project_id, | |
| "status": "completed", | |
| "completed_at": project["completed_at"], | |
| "services": project["services"], | |
| "chat_id": project.get("chat_id") # ADDED: Include chat_id in webhook | |
| } | |
| response = requests.post( | |
| project["n8n_webhook"], | |
| json=webhook_data, | |
| timeout=10, | |
| headers={"Content-Type": "application/json"} | |
| ) | |
| print(f"π€ Webhook sent: {response.status_code}") | |
| except Exception as e: | |
| print(f"β οΈ Webhook error: {e}") | |
| return { | |
| "status": "updated", | |
| "project_id": project_id, | |
| "all_completed": all_completed | |
| } | |
| except Exception as e: | |
| print(f"β Update error: {e}") | |
| return JSONResponse(status_code=500, content={"error": str(e)}) | |
| async def get_status(project_id: str): | |
| """Get current status of a project""" | |
| if project_id not in projects: | |
| return JSONResponse(status_code=404, content={"error": "Project not found"}) | |
| project = projects[project_id] | |
| all_completed = all( | |
| s.get("status") == "completed" | |
| for s in project["services"].values() | |
| ) | |
| return { | |
| "project_id": project_id, | |
| "created_at": project["created_at"], | |
| "completed_at": project.get("completed_at"), | |
| "services": project["services"], | |
| "all_completed": all_completed | |
| } | |
| async def list_projects(): | |
| """List all active projects""" | |
| return { | |
| "active_projects": len(projects), | |
| "projects": list(projects.keys()) | |
| } | |
| # ============================================= | |
| # BACKGROUND CLEANUP TASK | |
| # ============================================= | |
| def cleanup_old_projects(): | |
| """Remove completed projects after 1 hour""" | |
| while True: | |
| time.sleep(300) # Check every 5 minutes | |
| now = datetime.now() | |
| to_delete = [] | |
| for pid, project in projects.items(): | |
| # Remove completed projects after 1 hour | |
| if project.get("completed_at"): | |
| completed_time = datetime.fromisoformat(project["completed_at"]) | |
| if (now - completed_time).total_seconds() > 3600: | |
| to_delete.append(pid) | |
| # Remove stuck projects after 2 hours | |
| elif (now - datetime.fromisoformat(project["created_at"])).total_seconds() > 7200: | |
| to_delete.append(pid) | |
| for pid in to_delete: | |
| del projects[pid] | |
| print(f"π§Ή Cleaned up project: {pid}") | |
| # Start cleanup thread | |
| cleanup_thread = threading.Thread(target=cleanup_old_projects, daemon=True) | |
| cleanup_thread.start() | |
| print("β Cleanup thread started") | |
| # ============================================= | |
| # STARTUP COMPLETE | |
| # ============================================= | |
| print("\n" + "=" * 60) | |
| print("β Status Tracker API ready!") | |
| print(f"π Active projects: {len(projects)}") | |
| print("\nπ‘ Endpoints (all public):") | |
| print(" - GET / - API info") | |
| print(" - GET /health - Health check") | |
| print(" - GET /test - Test endpoint") | |
| print(" - POST /register - Register project") | |
| print(" - POST /update - Update status") | |
| print(" - GET /status/ID - Get project status") | |
| print(" - GET /projects - List all projects") | |
| print("\nπ Base URL: https://yukee1992-status-tracker.hf.space") | |
| print("=" * 60) | |
| # ============================================= | |
| # RUN | |
| # ============================================= | |
| if __name__ == "__main__": | |
| uvicorn.run(app, host="0.0.0.0", port=7860) |