WP11 #8

Merged
Lars merged 30 commits from WP11 into main 2025-12-11 17:00:38 +01:00
Showing only changes of commit 5aae33f578 - Show all commits

View File

@ -1,7 +1,7 @@
""" """
app/routers/ingest.py app/routers/ingest.py
API-Endpunkte für WP-11 (Discovery & Persistence). API-Endpunkte für WP-11 (Discovery & Persistence).
Robustified for Frontend Integration. Fixed Async/Await Issues.
""" """
import os import os
import time import time
@ -11,14 +11,15 @@ from pydantic import BaseModel
from typing import Optional, List, Dict, Any from typing import Optional, List, Dict, Any
from app.core.ingestion import IngestionService 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 Konfiguration
logger = logging.getLogger("uvicorn.error") logger = logging.getLogger(__name__)
router = APIRouter() router = APIRouter()
# --- DTOs (Data Transfer Objects) --- # --- DTOs ---
class AnalyzeRequest(BaseModel): class AnalyzeRequest(BaseModel):
text: str text: str
@ -27,7 +28,7 @@ class AnalyzeRequest(BaseModel):
class SaveRequest(BaseModel): class SaveRequest(BaseModel):
markdown_content: str markdown_content: str
filename: Optional[str] = None filename: Optional[str] = None
folder: str = "00_Inbox" # Standard-Ordner folder: str = "00_Inbox"
class SaveResponse(BaseModel): class SaveResponse(BaseModel):
status: str status: str
@ -35,18 +36,52 @@ class SaveResponse(BaseModel):
note_id: str note_id: str
stats: Dict[str, Any] stats: Dict[str, Any]
# --- Services --- # --- Endpoints ---
discovery_service = DiscoveryService()
@router.post("/analyze") @router.post("/analyze")
async def analyze_draft(req: AnalyzeRequest): 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: try:
# Prio 2: Intelligence Service aufrufen # Wir nutzen den Retriever direkt, statt eines extra DiscoveryServices
result = await discovery_service.analyze_draft(req.text, req.type) retriever = Retriever()
return result 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: except Exception as e:
logger.error(f"Analyze failed: {e}", exc_info=True) logger.error(f"Analyze failed: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=f"Analysis failed: {str(e)}") 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. WP-11 Persistence: Speichert Markdown physisch und indiziert es sofort.
""" """
try: try:
# 1. Vault Root sicher ermitteln # 1. Pfad-Setup
vault_root = os.getenv("MINDNET_VAULT_ROOT", "./vault") 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) abs_vault_root = os.path.abspath(vault_root)
if not os.path.exists(abs_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" # Versuche ihn zu erstellen, falls er fehlt
logger.error(error_msg) os.makedirs(abs_vault_root, exist_ok=True)
raise HTTPException(status_code=500, detail=error_msg)
# 2. Filename Fallback # 2. Filename
final_filename = req.filename final_filename = req.filename
if not final_filename: if not final_filename:
final_filename = f"draft_{int(time.time())}.md" final_filename = f"draft_{int(time.time())}.md"
# 3. Ingestion Service aufrufen # 3. Ingestion Service
ingest_service = IngestionService() 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, markdown_content=req.markdown_content,
filename=final_filename, filename=final_filename,
vault_root=abs_vault_root, vault_root=abs_vault_root,
folder=req.folder folder=req.folder
) )
# Fehler vom Service abfangen # Fehlerprüfung auf dem Dictionary
if result.get("status") == "error": if result.get("status") == "error":
raise HTTPException(status_code=500, detail=result.get("error")) raise HTTPException(status_code=500, detail=result.get("error"))
return SaveResponse( return SaveResponse(
status="success", status="success",
file_path=result["path"], file_path=result.get("path", "unknown"),
note_id=result.get("note_id", "unknown"), note_id=result.get("note_id", "unknown"),
stats={ stats={
"chunks": result.get("chunks_count", 0), "chunks": result.get("chunks_count", 0),