Files
espiritismo-rag-system/backend/app/main.py
T
lgcosta 7af7ba530d fix: make docker compose build and start the API
The image context was backend/, so COPY frontend/ failed. Build from the
repo root, place Python modules on the import path, and pin packages that
exist on PyPI. The web process no longer loads the LLM at startup.
2026-11-01 12:00:08 -03:00

165 lines
5.4 KiB
Python

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"}