diff --git a/app.py b/app.py index 58d2706..20c00eb 100644 --- a/app.py +++ b/app.py @@ -1,52 +1,173 @@ import os import sys +from pathlib import Path +from fastapi import FastAPI, HTTPException +from fastapi.staticfiles import StaticFiles +from fastapi.responses import FileResponse, JSONResponse +from fastapi.middleware.cors import CORSMiddleware  -# Set environment variables for Hugging Face -os.environ["TRANSFORMERS_CACHE"] = "/tmp/model_cache" -os.environ["HF_HOME"] = "/tmp/model_cache" -os.environ["SENTENCE_TRANSFORMERS_HOME"] = "/tmp/model_cache" -os.environ["PYTHONUNBUFFERED"] = "1" - -# Print startup diagnostic info +print("=" * 60) print("=== STARTUP: Beginning application initialization ===") -print(f"=== STARTUP: PORT environment variable: {os.environ.get('PORT')} ===") +print("=" * 60)  -# First import just the app from main -from main import app +# Add current directory to Python path so imports work +current_path = os.path.dirname(__file__) +if current_path not in sys.path: + sys.path.insert(0, current_path)  -# THEN import other components -from main import conversation_memory -from data_integration.alu_api_connector import ALUDataConnector -from analytics.conversation_analytics import ConversationAnalytics +# Ensure required directories exist +os.makedirs("build/static", exist_ok=True) +os.makedirs("build/assets", exist_ok=True)  -@app.get("/api/alu-events") -async def get_alu_events(campus: str = "all", days: int = 7): - """Get upcoming events at ALU""" +# Load comprehensive knowledge base on startup +def load_comprehensive_knowledge_base(): + """Load the comprehensive ALU knowledge base into vector store""" try: - alu_connector = ALUDataConnector() - events = alu_connector.get_upcoming_events(campus, days) - return {"events": events} + print("\nšŸŽ“ Loading comprehensive ALU knowledge base...") + from retrieval_engine import RetrievalEngine +  + # Check for knowledge base files + kb_paths = [ + Path("data/alu_knowledge"), + Path("backend/data/alu_knowledge"), + Path("/data/alu_knowledge") + ] +  + kb_dir = None + for path in kb_paths: + if path.exists(): + kb_dir = path + break +  + if not kb_dir: + print("āš ļø Knowledge base directory not found. Using default ALU Brain.") + return False +  + # Find knowledge base file + kb_files = [ + "alu_ultimate_knowledge_base.txt", + "alu_knowledge_base.txt" + ] +  + kb_file = None + for filename in kb_files: + file_path = kb_dir / filename + if file_path.exists(): + kb_file = file_path + break +  + if not kb_file: + print("āš ļø Knowledge base file not found. Using default ALU Brain.") + return False +  + print(f"šŸ“„ Found knowledge base: {kb_file.name}") +  + # Load and process + with open(kb_file, 'r', encoding='utf-8') as f: + kb_text = f.read() +  + print(f"āœ… Loaded {len(kb_text):,} characters") +  + # Initialize retrieval engine + retrieval_engine = RetrievalEngine() +  + # Chunk text + chunks = retrieval_engine._chunk_text(kb_text, chunk_size=800, chunk_overlap=100) + print(f"āœ… Created {len(chunks)} chunks") +  + # Add to vector store + print("šŸ“„ Adding to vector store...") + for i, chunk in enumerate(chunks): + chunk_id = f"alu_comprehensive_kb_{i}" + metadata = { + "source": "ALU Comprehensive Knowledge Base 2024", + "chunk_id": i, + "type": "comprehensive_kb" + } +  + try: + retrieval_engine.collection.add( + ids=[chunk_id], + documents=[chunk], + metadatas=[metadata] + ) + except Exception as e: + if i == 0: # Only print error for first chunk + print(f"āš ļø Note: {e}") +  + print(f"āœ… Comprehensive knowledge base loaded! ({len(chunks)} chunks)") + return True +  except Exception as e: - print(f"Error fetching ALU events: {e}") - return {"events": [], "error": "Could not fetch events"} + print(f"āš ļø Error loading comprehensive KB: {e}") + return False + +# Load knowledge base (commented out to prevent startup timeout) +# Uncomment this line once the Space is stable: +# load_comprehensive_knowledge_base() +print("āš ļø Comprehensive KB loading disabled to prevent timeout") +print("šŸ’” The chatbot will use the existing ALU Brain knowledge base")  -@app.get("/api/analytics/dashboard") -async def get_analytics_dashboard(): - """Get analytics dashboard data""" +# Import your backend app (using minimal version to avoid model loading issues) +print("\nšŸ“¦ Importing minimal backend application...") +try: + from minimal_app import app as backend_app + print("āœ… Minimal backend application imported (no ML models required)") +except ImportError: + print("āš ļø Minimal backend not found, trying lightweight...") try: - if not conversation_memory: - return {"error": "Conversation memory not initialized"} -  - analytics = ConversationAnalytics(conversation_memory) - dashboard_data = analytics.generate_dashboard_data() - return dashboard_data - except Exception as e: - print(f"Error generating analytics dashboard: {e}") - return {"error": f"Could not generate analytics: {str(e)}"} - -# This is needed for Hugging Face Spaces -if __name__ == "__main__": - import uvicorn - port = int(os.environ.get("PORT", 7860)) # Hugging Face uses port 7860 - print(f"Starting server on port {port}") - uvicorn.run(app, host="0.0.0.0", port=port) \ No newline at end of file + from main_lightweight import app as backend_app + print("āœ… Lightweight backend application imported") + except ImportError: + print("āš ļø Using main backend...") + from main import app as backend_app + print("āœ… Main backend application imported") + +app = FastAPI() + +# Configure CORS - copy settings from your backend +allowed_origins = os.getenv("CORS_ALLOWED_ORIGINS", "http://localhost:3000,http://localhost:3001").split(",") +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # Allow all origins when serving from same domain + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Serve static files from React build +app.mount("/static", StaticFiles(directory="build/static"), name="static") +app.mount("/assets", StaticFiles(directory="build/assets", check_dir=False), name="assets") + +# Mount your backend API - all backend routes will be served under /api +# IMPORTANT: Mount this AFTER static files but BEFORE catch-all route +app.mount("/api", backend_app) + +# Root endpoint - return JSON instead of trying to serve files +@app.get("/") +async def root(): + return { + "status": "running", + "message": "ALU Student Companion API", + "api_endpoints": { + "health": "/health", + "chat": "/api/chat", + "docs": "/docs" + } + } + +# Health check endpoint +@app.get("/health") +async def health(): + return {"status": "healthy", "api": True, "backend": True} + +# Serve React app for specific paths only (not catch-all) +@app.get("/static/{full_path:path}") +async def serve_static(full_path: str): + file_path = f"build/static/{full_path}" + if os.path.exists(file_path): + return FileResponse(file_path) + return JSONResponse( + content={"error": "File not found"}, + status_code=404 + )