diff --git a/app/routers/ingest.py b/app/routers/ingest.py index ce729db..95b2369 100644 --- a/app/routers/ingest.py +++ b/app/routers/ingest.py @@ -1,7 +1,7 @@ """ app/routers/ingest.py API-Endpunkte für WP-11 (Discovery & Persistence). -Robustified for Frontend Integration. +Fixed Async/Await Issues. """ import os import time @@ -11,14 +11,15 @@ from pydantic import BaseModel from typing import Optional, List, Dict, Any from app.core.ingestion import IngestionService -from app.services.discovery import DiscoveryService +from app.core.retriever import Retriever +from app.models.dto import QueryRequest -# Logger für Backend-Debugging aktivieren -logger = logging.getLogger("uvicorn.error") +# Logger Konfiguration +logger = logging.getLogger(__name__) router = APIRouter() -# --- DTOs (Data Transfer Objects) --- +# --- DTOs --- class AnalyzeRequest(BaseModel): text: str @@ -27,7 +28,7 @@ class AnalyzeRequest(BaseModel): class SaveRequest(BaseModel): markdown_content: str filename: Optional[str] = None - folder: str = "00_Inbox" # Standard-Ordner + folder: str = "00_Inbox" class SaveResponse(BaseModel): status: str @@ -35,18 +36,52 @@ class SaveResponse(BaseModel): note_id: str stats: Dict[str, Any] -# --- Services --- -discovery_service = DiscoveryService() +# --- Endpoints --- @router.post("/analyze") async def analyze_draft(req: AnalyzeRequest): """ - WP-11 Intelligence: Liefert Link-Vorschläge basierend auf Text und Typ. + WP-11 Intelligence: Liefert Link-Vorschläge via Retriever. """ try: - # Prio 2: Intelligence Service aufrufen - result = await discovery_service.analyze_draft(req.text, req.type) - return result + # Wir nutzen den Retriever direkt, statt eines extra DiscoveryServices + retriever = Retriever() + suggestions = [] + + # 1. Suche nach ähnlichen Inhalten (Semantic) + query_text = req.text[:400] + if not query_text.strip(): + return {"suggestions": []} + + # Check ob Retriever async ist (in v2.3 oft ja) + if hasattr(retriever.search, '__await__'): + hits_result = await retriever.search(QueryRequest(query=query_text, top_k=5, mode="hybrid")) + else: + # Fallback sync + hits_result = await retriever.search(QueryRequest(query=query_text, top_k=5, mode="hybrid")) + + seen_titles = set() + for hit in hits_result.results: + title = hit.payload.get("note_id") or hit.node_id + if not title or title in seen_titles: continue + seen_titles.add(title) + + # Simple Edge Logic + edge_kind = "related_to" + if req.type == "project": edge_kind = "depends_on" + if req.type == "decision": edge_kind = "references" + + if hit.total_score > 0.65: + suggestions.append({ + "target_title": title, + "target_id": hit.node_id, + "suggested_markdown": f"[[rel:{edge_kind} {title}]]", + "reason": f"Semantisch ähnlich ({hit.total_score:.2f})", + "type": "semantic" + }) + + return {"suggestions": suggestions} + except Exception as e: logger.error(f"Analyze failed: {e}", exc_info=True) raise HTTPException(status_code=500, detail=f"Analysis failed: {str(e)}") @@ -57,41 +92,40 @@ async def save_note(req: SaveRequest): WP-11 Persistence: Speichert Markdown physisch und indiziert es sofort. """ try: - # 1. Vault Root sicher ermitteln + # 1. Pfad-Setup vault_root = os.getenv("MINDNET_VAULT_ROOT", "./vault") - - # Absolute Pfade auflösen, um CWD-Probleme zu vermeiden abs_vault_root = os.path.abspath(vault_root) if not os.path.exists(abs_vault_root): - error_msg = f"Vault root not found at: {abs_vault_root}. Check MINDNET_VAULT_ROOT in .env" - logger.error(error_msg) - raise HTTPException(status_code=500, detail=error_msg) + # Versuche ihn zu erstellen, falls er fehlt + os.makedirs(abs_vault_root, exist_ok=True) - # 2. Filename Fallback + # 2. Filename final_filename = req.filename if not final_filename: final_filename = f"draft_{int(time.time())}.md" - # 3. Ingestion Service aufrufen + # 3. Ingestion Service ingest_service = IngestionService() - logger.info(f"Attempting to save {final_filename} to {req.folder} in {abs_vault_root}") + logger.info(f"Saving {final_filename} to {req.folder}") - result = ingest_service.create_from_text( + # --- DER FIX: AWAIT HINZUFÜGEN --- + # Da ingestion.py auf async umgestellt wurde, MÜSSEN wir hier warten. + result = await ingest_service.create_from_text( markdown_content=req.markdown_content, filename=final_filename, vault_root=abs_vault_root, folder=req.folder ) - # Fehler vom Service abfangen + # Fehlerprüfung auf dem Dictionary if result.get("status") == "error": raise HTTPException(status_code=500, detail=result.get("error")) return SaveResponse( status="success", - file_path=result["path"], + file_path=result.get("path", "unknown"), note_id=result.get("note_id", "unknown"), stats={ "chunks": result.get("chunks_count", 0),