Add file upload RAG: main.py + index.html
Browse files- app/main.py +38 -2
- static/index.html +13 -0
app/main.py
CHANGED
|
@@ -3,13 +3,14 @@
|
|
| 3 |
import re
|
| 4 |
from contextlib import asynccontextmanager
|
| 5 |
from pathlib import Path
|
|
|
|
| 6 |
|
| 7 |
-
from fastapi import FastAPI
|
| 8 |
from fastapi.responses import FileResponse
|
| 9 |
from fastapi.staticfiles import StaticFiles
|
| 10 |
from pydantic import BaseModel
|
| 11 |
|
| 12 |
-
from app.rag import load_corpus, retrieve
|
| 13 |
from app.llm import build_system_prompt, chat, analyze_session
|
| 14 |
|
| 15 |
|
|
@@ -25,6 +26,8 @@ app = FastAPI(title="AIM Learning Companion", lifespan=lifespan)
|
|
| 25 |
STATIC_DIR = Path(__file__).parent.parent / "static"
|
| 26 |
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
|
| 27 |
|
|
|
|
|
|
|
| 28 |
|
| 29 |
class ChatRequest(BaseModel):
|
| 30 |
message: str
|
|
@@ -84,6 +87,39 @@ async def api_chat(req: ChatRequest):
|
|
| 84 |
return ChatResponse(reply=reply, phase=detected_phase)
|
| 85 |
|
| 86 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
@app.post("/api/analyze", response_model=AnalysisResponse)
|
| 88 |
async def api_analyze(req: AnalysisRequest):
|
| 89 |
analysis = await analyze_session(req.history)
|
|
|
|
| 3 |
import re
|
| 4 |
from contextlib import asynccontextmanager
|
| 5 |
from pathlib import Path
|
| 6 |
+
from typing import List
|
| 7 |
|
| 8 |
+
from fastapi import FastAPI, UploadFile, File
|
| 9 |
from fastapi.responses import FileResponse
|
| 10 |
from fastapi.staticfiles import StaticFiles
|
| 11 |
from pydantic import BaseModel
|
| 12 |
|
| 13 |
+
from app.rag import load_corpus, retrieve, add_documents, list_documents, delete_document
|
| 14 |
from app.llm import build_system_prompt, chat, analyze_session
|
| 15 |
|
| 16 |
|
|
|
|
| 26 |
STATIC_DIR = Path(__file__).parent.parent / "static"
|
| 27 |
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
|
| 28 |
|
| 29 |
+
ALLOWED_EXTENSIONS = {".txt", ".pdf", ".pptx", ".ppt", ".zip"}
|
| 30 |
+
|
| 31 |
|
| 32 |
class ChatRequest(BaseModel):
|
| 33 |
message: str
|
|
|
|
| 87 |
return ChatResponse(reply=reply, phase=detected_phase)
|
| 88 |
|
| 89 |
|
| 90 |
+
@app.post("/api/upload")
|
| 91 |
+
async def api_upload(files: List[UploadFile] = File(...)):
|
| 92 |
+
"""Upload one or more files (PDF, PPTX, TXT, ZIP) to the RAG corpus."""
|
| 93 |
+
file_data = []
|
| 94 |
+
skipped = []
|
| 95 |
+
|
| 96 |
+
for f in files:
|
| 97 |
+
ext = Path(f.filename).suffix.lower() if f.filename else ""
|
| 98 |
+
if ext not in ALLOWED_EXTENSIONS:
|
| 99 |
+
skipped.append({"filename": f.filename, "reason": f"Type non supporte: {ext}"})
|
| 100 |
+
continue
|
| 101 |
+
content = await f.read()
|
| 102 |
+
file_data.append((f.filename, content))
|
| 103 |
+
|
| 104 |
+
results = add_documents(file_data) if file_data else []
|
| 105 |
+
return {"results": results, "skipped": skipped}
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
@app.get("/api/documents")
|
| 109 |
+
async def api_documents():
|
| 110 |
+
"""List all documents in the corpus."""
|
| 111 |
+
return {"documents": list_documents()}
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
@app.delete("/api/documents/{filename}")
|
| 115 |
+
async def api_delete_document(filename: str):
|
| 116 |
+
"""Delete a document from the corpus."""
|
| 117 |
+
ok = delete_document(filename)
|
| 118 |
+
if ok:
|
| 119 |
+
return {"status": "ok"}
|
| 120 |
+
return {"status": "error", "message": "Fichier non trouve"}
|
| 121 |
+
|
| 122 |
+
|
| 123 |
@app.post("/api/analyze", response_model=AnalysisResponse)
|
| 124 |
async def api_analyze(req: AnalysisRequest):
|
| 125 |
analysis = await analyze_session(req.history)
|
static/index.html
CHANGED
|
@@ -18,6 +18,18 @@
|
|
| 18 |
<input type="text" id="topic-input" placeholder="Ex : L'intelligence artificielle en formation professionnelle">
|
| 19 |
</div>
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
<div class="form-group">
|
| 22 |
<label>Mode</label>
|
| 23 |
<div class="mode-selector">
|
|
@@ -41,6 +53,7 @@
|
|
| 41 |
<div class="chat-header-left">
|
| 42 |
<span id="mode-badge" class="badge badge-mode"></span>
|
| 43 |
<span id="topic-badge" class="badge badge-topic"></span>
|
|
|
|
| 44 |
</div>
|
| 45 |
<div class="chat-header-right">
|
| 46 |
<button id="btn-end-session" class="btn-end">Terminer la session</button>
|
|
|
|
| 18 |
<input type="text" id="topic-input" placeholder="Ex : L'intelligence artificielle en formation professionnelle">
|
| 19 |
</div>
|
| 20 |
|
| 21 |
+
<div class="form-group">
|
| 22 |
+
<label>Documents de reference (optionnel)</label>
|
| 23 |
+
<div class="upload-zone" id="upload-zone">
|
| 24 |
+
<div class="upload-icon">+</div>
|
| 25 |
+
<div class="upload-text">Glisse tes fichiers ici ou clique pour selectionner</div>
|
| 26 |
+
<div class="upload-hint">PDF, PPTX, TXT ou ZIP — plusieurs fichiers possibles</div>
|
| 27 |
+
<input type="file" id="file-input" multiple accept=".pdf,.pptx,.ppt,.txt,.zip" hidden>
|
| 28 |
+
</div>
|
| 29 |
+
<div class="upload-list" id="upload-list"></div>
|
| 30 |
+
<div class="upload-status" id="upload-status"></div>
|
| 31 |
+
</div>
|
| 32 |
+
|
| 33 |
<div class="form-group">
|
| 34 |
<label>Mode</label>
|
| 35 |
<div class="mode-selector">
|
|
|
|
| 53 |
<div class="chat-header-left">
|
| 54 |
<span id="mode-badge" class="badge badge-mode"></span>
|
| 55 |
<span id="topic-badge" class="badge badge-topic"></span>
|
| 56 |
+
<span id="docs-badge" class="badge badge-docs" style="display:none"></span>
|
| 57 |
</div>
|
| 58 |
<div class="chat-header-right">
|
| 59 |
<button id="btn-end-session" class="btn-end">Terminer la session</button>
|