From a7cda3f51cd3c0441e29fe681ee15ebad3318481 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 11 Dec 2025 08:18:41 +0100 Subject: [PATCH] bugfix --- app/core/ingestion.py | 27 +++++----- app/routers/ingest.py | 101 ++++++++++++++++++++++---------------- app/services/discovery.py | 37 ++++++++++---- 3 files changed, 102 insertions(+), 63 deletions(-) diff --git a/app/core/ingestion.py b/app/core/ingestion.py index 992c29d..80f9398 100644 --- a/app/core/ingestion.py +++ b/app/core/ingestion.py @@ -268,35 +268,38 @@ class IngestionService: markdown_content: str, filename: str, vault_root: str, - folder: str = "Inbox" # Standard-Ordner für neue Files + folder: str = "00_Inbox" ) -> Dict[str, Any]: """ - WP-11: Schreibt Text in eine physische Datei und indiziert sie sofort. + WP-11 Persistence: Schreibt Text sicher und indiziert ihn. + Erstellt Verzeichnisse automatisch. """ - # 1. Pfad vorbereiten + # 1. Zielordner vorbereiten target_dir = os.path.join(vault_root, folder) - os.makedirs(target_dir, exist_ok=True) + try: + os.makedirs(target_dir, exist_ok=True) + except Exception as e: + return {"status": "error", "error": f"Could not create folder {target_dir}: {e}"} - # Dateiname bereinigen (Sicherheit) + # 2. Dateiname bereinigen safe_filename = os.path.basename(filename) if not safe_filename.endswith(".md"): safe_filename += ".md" file_path = os.path.join(target_dir, safe_filename) - # 2. Schreiben (Write to Disk - Single Source of Truth) + # 3. Schreiben try: with open(file_path, "w", encoding="utf-8") as f: f.write(markdown_content) except Exception as e: - return {"status": "error", "error": f"Disk write failed: {str(e)}"} + return {"status": "error", "error": f"Disk write failed at {file_path}: {str(e)}"} - # 3. Indizieren (Ingest) - # Wir rufen einfach die existierende Logik auf! + # 4. Indizieren (Single File Upsert) return self.process_file( file_path=file_path, vault_root=vault_root, - apply=True, # Sofort schreiben - force_replace=True, # Da neu, erzwingen wir Update - purge_before=True # Sauberer Start + apply=True, + force_replace=True, + purge_before=True ) \ No newline at end of file diff --git a/app/routers/ingest.py b/app/routers/ingest.py index aa97f49..ce729db 100644 --- a/app/routers/ingest.py +++ b/app/routers/ingest.py @@ -1,9 +1,11 @@ """ app/routers/ingest.py API-Endpunkte für WP-11 (Discovery & Persistence). +Robustified for Frontend Integration. """ import os import time +import logging from fastapi import APIRouter, HTTPException from pydantic import BaseModel from typing import Optional, List, Dict, Any @@ -11,9 +13,12 @@ from typing import Optional, List, Dict, Any from app.core.ingestion import IngestionService from app.services.discovery import DiscoveryService +# Logger für Backend-Debugging aktivieren +logger = logging.getLogger("uvicorn.error") + router = APIRouter() -# --- DTOs --- +# --- DTOs (Data Transfer Objects) --- class AnalyzeRequest(BaseModel): text: str @@ -21,8 +26,8 @@ class AnalyzeRequest(BaseModel): class SaveRequest(BaseModel): markdown_content: str - filename: Optional[str] = None # Optional, fallback auf Timestamp - folder: str = "00_Inbox" # Zielordner + filename: Optional[str] = None + folder: str = "00_Inbox" # Standard-Ordner class SaveResponse(BaseModel): status: str @@ -36,54 +41,66 @@ discovery_service = DiscoveryService() @router.post("/analyze") async def analyze_draft(req: AnalyzeRequest): """ - WP-11 Intelligence: Analysiert einen Entwurf und liefert Link-Vorschläge. + WP-11 Intelligence: Liefert Link-Vorschläge basierend auf Text und Typ. """ try: + # Prio 2: Intelligence Service aufrufen result = await discovery_service.analyze_draft(req.text, req.type) return result 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 in Qdrant. + WP-11 Persistence: Speichert Markdown physisch und indiziert es sofort. """ - # 1. Vault Root ermitteln - vault_root = os.getenv("MINDNET_VAULT_ROOT", "./vault") - if not os.path.exists(vault_root): - # Fallback relative paths - if os.path.exists("vault"): - vault_root = "vault" - elif os.path.exists("../vault"): - vault_root = "../vault" - else: - raise HTTPException(status_code=500, detail="Vault root not configured or missing") - - # 2. Filename generieren falls fehlend - final_filename = req.filename - if not final_filename: - final_filename = f"draft_{int(time.time())}.md" - - # 3. Ingestion Service nutzen - ingest_service = IngestionService() - - result = ingest_service.create_from_text( - markdown_content=req.markdown_content, - filename=final_filename, - vault_root=os.path.abspath(vault_root), - folder=req.folder - ) - - if result.get("status") == "error": - raise HTTPException(status_code=500, detail=result.get("error")) + try: + # 1. Vault Root sicher ermitteln + vault_root = os.getenv("MINDNET_VAULT_ROOT", "./vault") - return SaveResponse( - status="success", - file_path=result["path"], - note_id=result.get("note_id", "unknown"), - stats={ - "chunks": result.get("chunks_count", 0), - "edges": result.get("edges_count", 0) - } - ) \ No newline at end of file + # 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) + + # 2. Filename Fallback + final_filename = req.filename + if not final_filename: + final_filename = f"draft_{int(time.time())}.md" + + # 3. Ingestion Service aufrufen + ingest_service = IngestionService() + + logger.info(f"Attempting to save {final_filename} to {req.folder} in {abs_vault_root}") + + result = 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 + if result.get("status") == "error": + raise HTTPException(status_code=500, detail=result.get("error")) + + return SaveResponse( + status="success", + file_path=result["path"], + 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)}") \ No newline at end of file diff --git a/app/services/discovery.py b/app/services/discovery.py index 7036382..15635ca 100644 --- a/app/services/discovery.py +++ b/app/services/discovery.py @@ -102,9 +102,12 @@ class DiscoveryService: path = os.getenv("MINDNET_TYPES_FILE", "config/types.yaml") if not os.path.exists(path): # Fallback relative Pfade - if os.path.exists("types.yaml"): path = "types.yaml" - elif os.path.exists("../config/types.yaml"): path = "../config/types.yaml" - else: return {} + if os.path.exists("types.yaml"): + path = "types.yaml" + elif os.path.exists("../config/types.yaml"): + path = "../config/types.yaml" + else: + return {} try: with open(path, "r", encoding="utf-8") as f: @@ -132,7 +135,7 @@ class DiscoveryService: # 3. Fallback, falls nichts konfiguriert ist return "related_to" - # --- Core Logic (Unverändert) --- + # --- Core Logic --- def _fetch_all_titles_and_aliases(self) -> List[Dict]: notes = [] @@ -150,15 +153,19 @@ class DiscoveryService: ) for point in res: pl = point.payload or {} + + # Aliases robust lesen (kann Liste oder String sein) aliases = pl.get("aliases") or [] - if isinstance(aliases, str): aliases = [aliases] + if isinstance(aliases, str): + aliases = [aliases] notes.append({ "id": pl.get("note_id"), "title": pl.get("title"), "aliases": aliases }) - if next_page is None: break + if next_page is None: + break except Exception as e: logger.error(f"Error fetching titles: {e}") return [] @@ -168,14 +175,25 @@ class DiscoveryService: found = [] text_lower = text.lower() for entity in entities: + # 1. Title Match title = entity.get("title") if title and title.lower() in text_lower: - found.append({"match": title, "title": title, "id": entity["id"]}) + found.append({ + "match": title, + "title": title, + "id": entity["id"] + }) continue + + # 2. Alias Match aliases = entity.get("aliases", []) for alias in aliases: if alias and str(alias).lower() in text_lower: - found.append({"match": alias, "title": title, "id": entity["id"]}) + found.append({ + "match": alias, + "title": title, + "id": entity["id"] + }) break return found @@ -184,5 +202,6 @@ class DiscoveryService: try: res = hybrid_retrieve(req) return res.results - except Exception: + except Exception as e: + logger.error(f"Semantic suggestion error: {e}") return [] \ No newline at end of file