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.
165 lines
5.4 KiB
Python
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"}
|