""" app/routers/ingest.py API-Endpunkte für WP-11 (Discovery & Persistence). Fixed Async/Await Issues. """ import os import time import logging from fastapi import APIRouter, HTTPException from pydantic import BaseModel from typing import Optional, List, Dict, Any from app.core.ingestion import IngestionService from app.core.retriever import Retriever from app.models.dto import QueryRequest # Logger Konfiguration logger = logging.getLogger(__name__) router = APIRouter() # --- DTOs --- class AnalyzeRequest(BaseModel): text: str type: str = "concept" class SaveRequest(BaseModel): markdown_content: str filename: Optional[str] = None folder: str = "00_Inbox" class SaveResponse(BaseModel): status: str file_path: str note_id: str stats: Dict[str, Any] # --- Endpoints --- @router.post("/analyze") async def analyze_draft(req: AnalyzeRequest): """ WP-11 Intelligence: Liefert Link-Vorschläge via Retriever. """ try: # 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)}") @router.post("/save", response_model=SaveResponse) async def save_note(req: SaveRequest): """ WP-11 Persistence: Speichert Markdown physisch und indiziert es sofort. """ try: # 1. Pfad-Setup vault_root = os.getenv("MINDNET_VAULT_ROOT", "./vault") abs_vault_root = os.path.abspath(vault_root) if not os.path.exists(abs_vault_root): # Versuche ihn zu erstellen, falls er fehlt os.makedirs(abs_vault_root, exist_ok=True) # 2. Filename final_filename = req.filename if not final_filename: final_filename = f"draft_{int(time.time())}.md" # 3. Ingestion Service ingest_service = IngestionService() logger.info(f"Saving {final_filename} to {req.folder}") # --- 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 ) # 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.get("path", "unknown"), note_id=result.get("note_id", "unknown"), stats={ "chunks": result.get("chunks_count", 0), "edges": result.get("edges_count", 0) } ) except HTTPException as he: raise he except Exception as e: logger.error(f"Save failed: {e}", exc_info=True) raise HTTPException(status_code=500, detail=f"Save failed: {str(e)}")