bugfix
This commit is contained in:
parent
d2ee48555a
commit
a7cda3f51c
|
|
@ -268,35 +268,38 @@ class IngestionService:
|
||||||
markdown_content: str,
|
markdown_content: str,
|
||||||
filename: str,
|
filename: str,
|
||||||
vault_root: str,
|
vault_root: str,
|
||||||
folder: str = "Inbox" # Standard-Ordner für neue Files
|
folder: str = "00_Inbox"
|
||||||
) -> Dict[str, Any]:
|
) -> 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)
|
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)
|
safe_filename = os.path.basename(filename)
|
||||||
if not safe_filename.endswith(".md"):
|
if not safe_filename.endswith(".md"):
|
||||||
safe_filename += ".md"
|
safe_filename += ".md"
|
||||||
|
|
||||||
file_path = os.path.join(target_dir, safe_filename)
|
file_path = os.path.join(target_dir, safe_filename)
|
||||||
|
|
||||||
# 2. Schreiben (Write to Disk - Single Source of Truth)
|
# 3. Schreiben
|
||||||
try:
|
try:
|
||||||
with open(file_path, "w", encoding="utf-8") as f:
|
with open(file_path, "w", encoding="utf-8") as f:
|
||||||
f.write(markdown_content)
|
f.write(markdown_content)
|
||||||
except Exception as e:
|
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)
|
# 4. Indizieren (Single File Upsert)
|
||||||
# Wir rufen einfach die existierende Logik auf!
|
|
||||||
return self.process_file(
|
return self.process_file(
|
||||||
file_path=file_path,
|
file_path=file_path,
|
||||||
vault_root=vault_root,
|
vault_root=vault_root,
|
||||||
apply=True, # Sofort schreiben
|
apply=True,
|
||||||
force_replace=True, # Da neu, erzwingen wir Update
|
force_replace=True,
|
||||||
purge_before=True # Sauberer Start
|
purge_before=True
|
||||||
)
|
)
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
import logging
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import Optional, List, Dict, Any
|
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.core.ingestion import IngestionService
|
||||||
from app.services.discovery import DiscoveryService
|
from app.services.discovery import DiscoveryService
|
||||||
|
|
||||||
|
# Logger für Backend-Debugging aktivieren
|
||||||
|
logger = logging.getLogger("uvicorn.error")
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
# --- DTOs ---
|
# --- DTOs (Data Transfer Objects) ---
|
||||||
|
|
||||||
class AnalyzeRequest(BaseModel):
|
class AnalyzeRequest(BaseModel):
|
||||||
text: str
|
text: str
|
||||||
|
|
@ -21,8 +26,8 @@ class AnalyzeRequest(BaseModel):
|
||||||
|
|
||||||
class SaveRequest(BaseModel):
|
class SaveRequest(BaseModel):
|
||||||
markdown_content: str
|
markdown_content: str
|
||||||
filename: Optional[str] = None # Optional, fallback auf Timestamp
|
filename: Optional[str] = None
|
||||||
folder: str = "00_Inbox" # Zielordner
|
folder: str = "00_Inbox" # Standard-Ordner
|
||||||
|
|
||||||
class SaveResponse(BaseModel):
|
class SaveResponse(BaseModel):
|
||||||
status: str
|
status: str
|
||||||
|
|
@ -36,54 +41,66 @@ discovery_service = DiscoveryService()
|
||||||
@router.post("/analyze")
|
@router.post("/analyze")
|
||||||
async def analyze_draft(req: AnalyzeRequest):
|
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:
|
try:
|
||||||
|
# Prio 2: Intelligence Service aufrufen
|
||||||
result = await discovery_service.analyze_draft(req.text, req.type)
|
result = await discovery_service.analyze_draft(req.text, req.type)
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
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)}")
|
||||||
|
|
||||||
@router.post("/save", response_model=SaveResponse)
|
@router.post("/save", response_model=SaveResponse)
|
||||||
async def save_note(req: SaveRequest):
|
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
|
try:
|
||||||
vault_root = os.getenv("MINDNET_VAULT_ROOT", "./vault")
|
# 1. Vault Root sicher ermitteln
|
||||||
if not os.path.exists(vault_root):
|
vault_root = os.getenv("MINDNET_VAULT_ROOT", "./vault")
|
||||||
# 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"))
|
|
||||||
|
|
||||||
return SaveResponse(
|
# Absolute Pfade auflösen, um CWD-Probleme zu vermeiden
|
||||||
status="success",
|
abs_vault_root = os.path.abspath(vault_root)
|
||||||
file_path=result["path"],
|
|
||||||
note_id=result.get("note_id", "unknown"),
|
if not os.path.exists(abs_vault_root):
|
||||||
stats={
|
error_msg = f"Vault root not found at: {abs_vault_root}. Check MINDNET_VAULT_ROOT in .env"
|
||||||
"chunks": result.get("chunks_count", 0),
|
logger.error(error_msg)
|
||||||
"edges": result.get("edges_count", 0)
|
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)}")
|
||||||
|
|
@ -102,9 +102,12 @@ class DiscoveryService:
|
||||||
path = os.getenv("MINDNET_TYPES_FILE", "config/types.yaml")
|
path = os.getenv("MINDNET_TYPES_FILE", "config/types.yaml")
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
# Fallback relative Pfade
|
# Fallback relative Pfade
|
||||||
if os.path.exists("types.yaml"): path = "types.yaml"
|
if os.path.exists("types.yaml"):
|
||||||
elif os.path.exists("../config/types.yaml"): path = "../config/types.yaml"
|
path = "types.yaml"
|
||||||
else: return {}
|
elif os.path.exists("../config/types.yaml"):
|
||||||
|
path = "../config/types.yaml"
|
||||||
|
else:
|
||||||
|
return {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
|
@ -132,7 +135,7 @@ class DiscoveryService:
|
||||||
# 3. Fallback, falls nichts konfiguriert ist
|
# 3. Fallback, falls nichts konfiguriert ist
|
||||||
return "related_to"
|
return "related_to"
|
||||||
|
|
||||||
# --- Core Logic (Unverändert) ---
|
# --- Core Logic ---
|
||||||
|
|
||||||
def _fetch_all_titles_and_aliases(self) -> List[Dict]:
|
def _fetch_all_titles_and_aliases(self) -> List[Dict]:
|
||||||
notes = []
|
notes = []
|
||||||
|
|
@ -150,15 +153,19 @@ class DiscoveryService:
|
||||||
)
|
)
|
||||||
for point in res:
|
for point in res:
|
||||||
pl = point.payload or {}
|
pl = point.payload or {}
|
||||||
|
|
||||||
|
# Aliases robust lesen (kann Liste oder String sein)
|
||||||
aliases = pl.get("aliases") or []
|
aliases = pl.get("aliases") or []
|
||||||
if isinstance(aliases, str): aliases = [aliases]
|
if isinstance(aliases, str):
|
||||||
|
aliases = [aliases]
|
||||||
|
|
||||||
notes.append({
|
notes.append({
|
||||||
"id": pl.get("note_id"),
|
"id": pl.get("note_id"),
|
||||||
"title": pl.get("title"),
|
"title": pl.get("title"),
|
||||||
"aliases": aliases
|
"aliases": aliases
|
||||||
})
|
})
|
||||||
if next_page is None: break
|
if next_page is None:
|
||||||
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error fetching titles: {e}")
|
logger.error(f"Error fetching titles: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
@ -168,14 +175,25 @@ class DiscoveryService:
|
||||||
found = []
|
found = []
|
||||||
text_lower = text.lower()
|
text_lower = text.lower()
|
||||||
for entity in entities:
|
for entity in entities:
|
||||||
|
# 1. Title Match
|
||||||
title = entity.get("title")
|
title = entity.get("title")
|
||||||
if title and title.lower() in text_lower:
|
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
|
continue
|
||||||
|
|
||||||
|
# 2. Alias Match
|
||||||
aliases = entity.get("aliases", [])
|
aliases = entity.get("aliases", [])
|
||||||
for alias in aliases:
|
for alias in aliases:
|
||||||
if alias and str(alias).lower() in text_lower:
|
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
|
break
|
||||||
return found
|
return found
|
||||||
|
|
||||||
|
|
@ -184,5 +202,6 @@ class DiscoveryService:
|
||||||
try:
|
try:
|
||||||
res = hybrid_retrieve(req)
|
res = hybrid_retrieve(req)
|
||||||
return res.results
|
return res.results
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
logger.error(f"Semantic suggestion error: {e}")
|
||||||
return []
|
return []
|
||||||
Loading…
Reference in New Issue
Block a user