""" API principale - FastAPI """ from pathlib import Path from fastapi import FastAPI, HTTPException from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse, HTMLResponse from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel import asyncio import sse_starlette.sse as sse import config from backend import wiki_ops, search, ingest, llm app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) class IngestRequest(BaseModel): source: str class ChatRequest(BaseModel): message: str history: list = [] @app.get("/") async def root(): return FileResponse("static/index.html") @app.get("/api/wiki/list") async def list_wiki(): return wiki_ops.list_wiki_pages(config.WIKI_ROOT) @app.get("/api/wiki/page/{path:path}") async def get_page(path: str): file_path = config.WIKI_ROOT / "wiki" / path if not file_path.exists(): # Prova con .md file_path = config.WIKI_ROOT / "wiki" / f"{path}.md" page_data = wiki_ops.read_page(file_path) if "error" in page_data: raise HTTPException(status_code=404, detail=page_data["error"]) return page_data @app.post("/api/ingest") async def ingest_source_endpoint(req: IngestRequest): source_path = config.WIKI_ROOT / "raw" / req.source if not source_path.exists(): raise HTTPException(status_code=404, detail=f"Source not found: {req.source}") result = await ingest.ingest_source(source_path) return result @app.get("/api/ingest/stream/{source}") async def ingest_stream(source: str): source_path = config.WIKI_ROOT / "raw" / source if not source_path.exists(): raise HTTPException(status_code=404, detail=f"Source not found: {source}") async def event_generator(): async for line in ingest.ingest_streaming(source_path): yield {"event": "message", "data": line} return sse.EventServerResponse(event_generator()) @app.get("/api/search") async def search_wiki(q: str = ""): if not q: return {"results": []} name_results = search.search_by_name(q) content_results = search.search_wiki(q) return { "by_name": name_results, "by_content": content_results } @app.get("/api/wiki/recent") async def recent_pages(limit: int = 10): return search.get_recent_pages(limit) @app.post("/api/chat") async def chat(req: ChatRequest): # Costruisci context dalla wiki wiki_index = wiki_ops.read_page(config.WIKI_ROOT / "wiki" / "index.md") overview = wiki_ops.read_page(config.WIKI_ROOT / "wiki" / "overview.md") system_prompt = f"""Sei un assistente che risponde basandosi sulla wiki. USA SEMPRE [[pagina]] per citare le fonti. Wiki Index: {wiki_index.get('content', '')} Overview: {overview.get('content', '')} Rispondi in italiano. Cita le fonti con [[nome-pagina]].""" messages = [{"role": "system", "content": system_prompt}] messages.extend(req.history) messages.append({"role": "user", "content": req.message}) async def event_generator(): async for token in llm.llm_chat(messages): yield {"event": "message", "data": token} return sse.EventServerResponse(event_generator()) @app.get("/api/raw/list") async def list_raw(): """Lista i file nella directory raw.""" raw_dir = config.WIKI_ROOT / "raw" result = {} for category in ["articles", "papers", "repos", "assets"]: cat_dir = raw_dir / category if cat_dir.exists(): files = [f.name for f in cat_dir.iterdir() if f.is_file()] result[category] = files else: result[category] = [] return result if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=config.WEB_PORT)