import json import os from typing import List from fastapi import FastAPI, File, HTTPException, UploadFile from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from app.models import ( BookInfo, ChatRequest, ChatResponse, ScheduleInfo, ScheduleRequest, UploadResponse, ) from app.services.bot_service import BotService from app.services.rag_service import QdrantService, RAGService from app.utils.schedule_manager import ScheduleManager BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) SCHEDULES_PATH = os.getenv("SCHEDULES_PATH", "/app/schedules.json") UPLOAD_DIR = os.getenv("UPLOAD_DIR", "/app/uploads") QDRANT_URL = os.getenv("QDRANT_URL", "http://qdrant:6333") MODEL_PATH = os.getenv("GPT4ALL_MODEL", "/app/models/llama-3.2-3b-instruct-q4_0.gguf") qdrant_service: QdrantService | None = None rag_service: RAGService | None = None bot_service = BotService(os.getenv("TELEGRAM_BOT_TOKEN", ""), os.getenv("TELEGRAM_CHAT_ID", "")) schedule_manager = ScheduleManager(SCHEDULES_PATH) app = FastAPI(title="Espiritismo RAG API") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.mount( "/static", StaticFiles(directory=os.path.join(BASE_DIR, "frontend", "static")), name="static", ) def get_qdrant() -> QdrantService: global qdrant_service if qdrant_service is None: qdrant_service = QdrantService(QDRANT_URL) return qdrant_service def get_rag() -> RAGService: global rag_service if rag_service is None: rag_service = RAGService(get_qdrant(), model_name=MODEL_PATH) return rag_service def load_schedules(): if os.path.isfile(SCHEDULES_PATH): with open(SCHEDULES_PATH, "r", encoding="utf-8") as handle: data = json.load(handle) return data if isinstance(data, list) else [] return [] def save_schedules(schedules: list): parent = os.path.dirname(SCHEDULES_PATH) if parent: os.makedirs(parent, exist_ok=True) with open(SCHEDULES_PATH, "w", encoding="utf-8") as handle: json.dump(schedules, handle, indent=2) @app.get("/health") async def health(): return {"status": "ok"} @app.get("/", response_class=HTMLResponse) async def index(): template = os.path.join(BASE_DIR, "templates", "index.html") with open(template, "r", encoding="utf-8") as handle: return handle.read() @app.get("/api/books", response_model=List[BookInfo]) async def get_books(): books = get_qdrant().get_books() return [{"title": book, "source": book} for book in books] @app.post("/api/chat", response_model=ChatResponse) async def chat(request: ChatRequest): try: return get_rag().query(request.message, request.top_k) except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc @app.post("/api/upload", response_model=UploadResponse) async def upload_pdf(file: UploadFile = File(...)): if not file.filename or not file.filename.endswith(".pdf"): raise HTTPException(status_code=400, detail="Only PDF files are allowed") os.makedirs(UPLOAD_DIR, exist_ok=True) file_path = os.path.join(UPLOAD_DIR, os.path.basename(file.filename)) try: content = await file.read() with open(file_path, "wb") as handle: handle.write(content) return UploadResponse(message="File uploaded successfully", filename=file.filename) except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc @app.get("/api/schedules", response_model=List[ScheduleInfo]) async def get_schedules(): return [ScheduleInfo(**item) for item in load_schedules()] @app.post("/api/schedules", response_model=ScheduleInfo) async def create_schedule(request: ScheduleRequest): schedules = load_schedules() new_schedule = schedule_manager.add_schedule(request.model_dump()) if not any(item.get("id") == new_schedule["id"] for item in schedules): schedules.append(new_schedule) save_schedules(schedules) try: bot_service.add_schedule(new_schedule) except Exception as exc: print(f"Warning: Could not add schedule to bot: {exc}") return ScheduleInfo(**new_schedule) @app.put("/api/schedules/{schedule_id}", response_model=ScheduleInfo) async def update_schedule(schedule_id: int, request: ScheduleRequest): updated = schedule_manager.update_schedule(schedule_id, request.model_dump()) if updated is None: raise HTTPException(status_code=404, detail="Schedule not found") save_schedules(schedule_manager.list_schedules()) try: bot_service.update_schedule(updated) except Exception as exc: print(f"Warning: Could not update schedule in bot: {exc}") return ScheduleInfo(**updated) @app.delete("/api/schedules/{schedule_id}") async def delete_schedule(schedule_id: int): current = schedule_manager.get_schedule(schedule_id) if current is None or not schedule_manager.delete_schedule(schedule_id): raise HTTPException(status_code=404, detail="Schedule not found") save_schedules(schedule_manager.list_schedules()) try: bot_service.remove_schedule(current) except Exception as exc: print(f"Warning: Could not remove schedule from bot: {exc}") return {"message": "Schedule deleted"}