gateway/routes/workflows.py
2025-04-21 17:44:28 +02:00

365 lines
No EOL
12 KiB
Python

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