125 lines
4.3 KiB
Python
125 lines
4.3 KiB
Python
"""
|
|
FILE: app/routers/ingest.py
|
|
DESCRIPTION: Endpunkte für WP-11. Nimmt Markdown entgegen.
|
|
Refactored für WP-14: Nutzt BackgroundTasks für non-blocking Save.
|
|
Update WP-20: Unterstützung für Hybrid-Cloud-Analyse Feedback.
|
|
VERSION: 0.8.0 (WP-20 Hybrid Ready)
|
|
STATUS: Active
|
|
DEPENDENCIES: app.core.ingestion, app.services.discovery, fastapi, pydantic
|
|
"""
|
|
|
|
import os
|
|
import time
|
|
import logging
|
|
import asyncio
|
|
from fastapi import APIRouter, HTTPException, BackgroundTasks
|
|
from pydantic import BaseModel
|
|
from typing import Optional, Dict, Any
|
|
|
|
from app.core.ingestion import IngestionService
|
|
from app.services.discovery import DiscoveryService
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter()
|
|
|
|
# Services Init
|
|
discovery_service = DiscoveryService()
|
|
|
|
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
|
|
message: str # Neu für UX Feedback
|
|
stats: Dict[str, Any] # Kann leer sein bei async processing
|
|
|
|
# --- Background Task Wrapper ---
|
|
async def run_ingestion_task(markdown_content: str, filename: str, vault_root: str, folder: str):
|
|
"""
|
|
Führt die Ingestion im Hintergrund aus, damit der Request nicht blockiert.
|
|
Integrierter WP-20 Hybrid-Modus über den IngestionService.
|
|
"""
|
|
logger.info(f"🔄 Background Task started: Ingesting {filename}...")
|
|
try:
|
|
ingest_service = IngestionService()
|
|
result = await ingest_service.create_from_text(
|
|
markdown_content=markdown_content,
|
|
filename=filename,
|
|
vault_root=vault_root,
|
|
folder=folder
|
|
)
|
|
# Hier könnte man später Notification-Services (Websockets) triggern
|
|
if result.get("status") == "error":
|
|
logger.error(f"❌ Background Ingestion Error for {filename}: {result.get('error')}")
|
|
else:
|
|
logger.info(f"✅ Background Task finished: {filename} ({result.get('chunks_count')} Chunks)")
|
|
|
|
except Exception as e:
|
|
logger.error(f"❌ Critical Background Task Failure: {e}", exc_info=True)
|
|
|
|
|
|
@router.post("/analyze")
|
|
async def analyze_draft(req: AnalyzeRequest):
|
|
"""
|
|
WP-11 Intelligence: Liefert Link-Vorschläge via DiscoveryService.
|
|
"""
|
|
try:
|
|
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)
|
|
return {"suggestions": [], "error": str(e)}
|
|
|
|
@router.post("/save", response_model=SaveResponse)
|
|
async def save_note(req: SaveRequest, background_tasks: BackgroundTasks):
|
|
"""
|
|
WP-14 Fix: Startet Ingestion im Hintergrund (Fire & Forget).
|
|
Verhindert Timeouts bei aktiver Smart-Edge-Allocation (WP-15) und Cloud-Hybrid-Modus (WP-20).
|
|
"""
|
|
try:
|
|
vault_root = os.getenv("MINDNET_VAULT_ROOT", "./vault")
|
|
abs_vault_root = os.path.abspath(vault_root)
|
|
|
|
if not os.path.exists(abs_vault_root):
|
|
try:
|
|
os.makedirs(abs_vault_root, exist_ok=True)
|
|
except Exception as e:
|
|
logger.warning(f"Could not create vault root: {e}")
|
|
|
|
final_filename = req.filename or f"draft_{int(time.time())}.md"
|
|
|
|
# Wir geben sofort eine ID zurück (optimistisch),
|
|
# auch wenn die echte ID erst nach dem Parsing feststeht.
|
|
# Für UI-Feedback nutzen wir den Filename.
|
|
|
|
# Task in die Queue schieben
|
|
background_tasks.add_task(
|
|
run_ingestion_task,
|
|
markdown_content=req.markdown_content,
|
|
filename=final_filename,
|
|
vault_root=abs_vault_root,
|
|
folder=req.folder
|
|
)
|
|
|
|
return SaveResponse(
|
|
status="queued",
|
|
file_path=os.path.join(req.folder, final_filename),
|
|
note_id="pending",
|
|
message="Speicherung & Hybrid-KI-Analyse (WP-20) im Hintergrund gestartet.",
|
|
stats={
|
|
"chunks": -1, # Indikator für Async
|
|
"edges": -1
|
|
}
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Save dispatch failed: {e}", exc_info=True)
|
|
raise HTTPException(status_code=500, detail=f"Save dispatch failed: {str(e)}") |