""" 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. VERSION: 0.7.0 (Fix: Timeout WP-14) 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. """ 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). """ 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: pass 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 & KI-Analyse 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)}")