import os import json from fastapi import APIRouter, HTTPException, Depends, Body, Path, Query from typing import List, Dict, Any, Optional from fastapi import status import asyncio import uuid from datetime import datetime import logging from dataclasses import dataclass # Import interfaces from modules.lucydom_interface import get_lucydom_interface from modules.auth import get_current_active_user, get_user_context from modules.chat import get_chat_manager # Import models import modules.lucydom_model as lucydom_model # Logger konfigurieren logger = logging.getLogger(__name__) # Router für Workflow-Endpunkte erstellen router = APIRouter( prefix="/api/workflows", tags=["Workflow"], responses={404: {"description": "Not found"}} ) @dataclass class AppContext: """Kontext-Objekt für alle benötigten Verbindungen und Benutzerinformationen""" mandate_id: int user_id: int interface_data: Any # LucyDOM Interface interface_chat: Any # Chat Manager async def get_context(current_user: Dict[str, Any]) -> AppContext: """ Erstellt ein zentrales Kontext-Objekt mit allen benötigten Interfaces Args: current_user: Aktueller Benutzer aus der Authentifizierung Returns: AppContext-Objekt mit allen benötigten Verbindungen """ mandate_id, user_id = await get_user_context(current_user) interface_data = get_lucydom_interface(mandate_id, user_id) interface_chat = get_chat_manager(mandate_id, user_id) return AppContext( mandate_id=mandate_id, user_id=user_id, interface_data=interface_data, interface_chat=interface_chat ) @router.get("", response_model=List[Dict[str, Any]]) async def list_workflows(current_user: Dict[str, Any] = Depends(get_current_active_user)): """Listet alle Workflows des Benutzers auf""" context = await get_context(current_user) # Workflows für den Benutzer abrufen workflows = context.interface_data.get_workflows_by_user(context.user_id) return workflows @router.post("/{workflow_id}/user-input", response_model=Dict[str, Any]) async def submit_user_input( workflow_id: Optional[str] = Path(None, description="ID des Workflows (optional)"), user_input: lucydom_model.UserInputRequest = Body(...), current_user: Dict[str, Any] = Depends(get_current_active_user) ): """ Ermöglicht es dem Benutzer, Eingaben für einen laufenden Workflow zu senden oder einen neuen Workflow zu starten. """ context = await get_context(current_user) # Improved logging logger.info(f"Benutzereingabe für Workflow {workflow_id or 'neu'} empfangen") try: # Workflow mit dem Chat-Manager fortsetzen oder neu starten user_input_dict = { "prompt": user_input.prompt, "list_file_id": user_input.list_file_id } workflow = await context.interface_chat.chat_run(user_input_dict, workflow_id) if not workflow: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Fehler bei der Verarbeitung der Benutzereingabe" ) return { "workflow_id": workflow.get("id"), "status": "processing", "message": "Benutzereingabe wurde empfangen und wird verarbeitet" } except HTTPException: # HTTP-Exceptions weiterleiten raise except Exception as e: logger.error(f"Fehler bei der Verarbeitung der Benutzereingabe: {str(e)}", exc_info=True) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Fehler bei der Verarbeitung der Benutzereingabe: {str(e)}" ) @router.post("/{workflow_id}/stop", response_model=Dict[str, Any]) async def stop_workflow( workflow_id: str = Path(..., description="ID des zu stoppenden Workflows"), current_user: Dict[str, Any] = Depends(get_current_active_user) ): """Stoppt einen laufenden Workflow""" context = await get_context(current_user) # Workflow laden workflow = context.interface_data.get_workflow(workflow_id) if not workflow: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Workflow mit ID {workflow_id} nicht gefunden" ) # Status auf "stopped" setzen workflow["status"] = "stopped" workflow["last_activity"] = datetime.now().isoformat() # Workflow aktualisieren context.interface_data.update_workflow(workflow_id, workflow) return { "workflow_id": workflow_id, "status": "stopped", "message": "Workflow wurde gestoppt" } @router.delete("/{workflow_id}", response_model=Dict[str, Any]) async def delete_workflow( workflow_id: str, current_user: Dict[str, Any] = Depends(get_current_active_user) ): """Löscht einen Workflow""" context = await get_context(current_user) # Workflow löschen success = context.interface_data.delete_workflow(workflow_id) if not success: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Workflow mit ID {workflow_id} nicht gefunden" ) return { "workflow_id": workflow_id, "message": "Workflow wurde gelöscht" } @router.get("/{workflow_id}/data-statistics", response_model=Dict[str, Any]) async def get_workflow_data_statistics( workflow_id: str, current_user: Dict[str, Any] = Depends(get_current_active_user) ): """ Gibt Statistiken über die übertragenen Datenmengen für einen Workflow zurück. """ context = await get_context(current_user) # Workflow laden workflow = context.interface_data.get_workflow(workflow_id) if not workflow: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Workflow mit ID {workflow_id} nicht gefunden" ) # Datenstatistiken zurückgeben data_stats = workflow.get("data_stats", {}) if not data_stats: data_stats = { "total_processing_time": 0.0, "total_token_count": 0, "total_bytes_sent": 0, "total_bytes_received": 0 } return { "workflow_id": workflow_id, "data_stats": data_stats } @router.get("/{workflow_id}/status", response_model=Dict[str, Any]) async def get_workflow_status( workflow_id: str, current_user: Dict[str, Any] = Depends(get_current_active_user) ): """Status eines Workflows abrufen""" context = await get_context(current_user) # Workflow laden workflow = context.interface_data.get_workflow(workflow_id) if not workflow: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Workflow mit ID {workflow_id} nicht gefunden" ) # Status aus dem geladenen Workflow erstellen status_info = { "id": workflow.get("id"), "name": workflow.get("name"), "status": workflow.get("status"), "started_at": workflow.get("started_at"), "last_activity": workflow.get("last_activity"), "data_stats": workflow.get("data_stats", {}) } return status_info @router.get("/{workflow_id}/logs", response_model=List[Dict[str, Any]]) async def get_workflow_logs( workflow_id: str, current_user: Dict[str, Any] = Depends(get_current_active_user) ): """Protokolle eines Workflows abrufen""" context = await get_context(current_user) # Logs abrufen logs = context.interface_data.get_workflow_logs(workflow_id) if not logs: # Prüfen, ob der Workflow existiert workflow = context.interface_data.get_workflow(workflow_id) if not workflow: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Workflow mit ID {workflow_id} nicht gefunden" ) # Leere Log-Liste zurückgeben logs = [] return logs @router.get("/{workflow_id}/messages", response_model=List[Dict[str, Any]]) async def get_workflow_messages( workflow_id: str, current_user: Dict[str, Any] = Depends(get_current_active_user) ): """Nachrichten eines Workflows abrufen""" context = await get_context(current_user) # Nachrichten abrufen messages = context.interface_data.get_workflow_messages(workflow_id) if messages is None: # Prüfen, ob der Workflow existiert workflow = context.interface_data.get_workflow(workflow_id) if not workflow: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Workflow mit ID {workflow_id} nicht gefunden" ) # Leere Nachrichtenliste zurückgeben messages = [] return messages @router.delete("/{workflow_id}/messages/{message_id}", response_model=Dict[str, Any]) async def delete_workflow_message( workflow_id: str = Path(..., description="ID des Workflows"), message_id: str = Path(..., description="ID der zu löschenden Nachricht"), current_user: Dict[str, Any] = Depends(get_current_active_user) ): """ Löscht eine einzelne Nachricht aus einem Workflow. Diese Funktion entfernt die Nachricht aus dem Workflow und auch aus der Datenbank. """ context = await get_context(current_user) try: # Prüfen, ob der Workflow existiert workflow = context.interface_data.get_workflow(workflow_id) if not workflow: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Workflow mit ID {workflow_id} nicht gefunden" ) # Nachricht löschen success = context.interface_data.delete_workflow_message(workflow_id, message_id) if not success: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Nachricht mit ID {message_id} im Workflow {workflow_id} nicht gefunden" ) return { "workflow_id": workflow_id, "message_id": message_id, "success": True, "message": "Nachricht erfolgreich gelöscht" } except HTTPException: # Bekannte HTTP-Exceptions weiterleiten raise except Exception as e: # Sonstige Fehler abfangen raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Fehler beim Löschen der Nachricht: {str(e)}" ) @router.delete("/{workflow_id}/messages/{message_id}/files/{file_id}", response_model=Dict[str, Any]) async def delete_file_from_message( workflow_id: str = Path(..., description="ID des Workflows"), message_id: str = Path(..., description="ID der Nachricht"), file_id: str = Path(..., description="ID der zu löschenden Datei"), current_user: Dict[str, Any] = Depends(get_current_active_user) ): """ Löscht eine einzelne Dateireferenz aus einer Nachricht im Workflow. Die Datei selbst wird nicht aus der Datenbank gelöscht, nur die Referenz in der Nachricht. """ context = await get_context(current_user) # Add detailed logging logger.debug(f"DELETE request: Remove file {file_id} from message {message_id} in workflow {workflow_id}") try: # Datei aus der Nachricht entfernen success = context.interface_data.delete_file_from_message(workflow_id, message_id, file_id) if not success: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Datei mit ID {file_id} in der Nachricht {message_id} nicht gefunden" ) return { "workflow_id": workflow_id, "message_id": message_id, "file_id": file_id, "success": True, "message": "Datei erfolgreich aus der Nachricht gelöscht" } except HTTPException: # HTTP-Exceptions weiterleiten raise except Exception as e: logger.error(f"Fehler beim Löschen der Datei: {str(e)}") import traceback traceback_str = traceback.format_exc() logger.error(f"Traceback: {traceback_str}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Fehler beim Löschen der Datei aus der Nachricht: {str(e)}" )