""" 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)}")