all components refactored

This commit is contained in:
ValueOn AG 2025-04-21 20:04:22 +02:00
parent 5c066feb19
commit f794872b45
11 changed files with 211 additions and 204 deletions

60
app.py
View file

@ -6,6 +6,7 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from fastapi.security import OAuth2PasswordRequestForm
from contextlib import asynccontextmanager
import uvicorn
from typing import Dict, Any
@ -17,8 +18,6 @@ import pathlib
from modules.configuration import APP_CONFIG
from modules.gateway_interface import get_gateway_interface
# Import auth module
from modules.auth import (
create_access_token,
@ -27,7 +26,7 @@ from modules.auth import (
ACCESS_TOKEN_EXPIRE_MINUTES
)
# Import models - generisch importieren zur INITIALISIERUNG, auch wenn dummy!
# Import models - import generically for INITIALIZATION, even if dummy!
import modules.gateway_model as gateway_model
import modules.lucydom_interface as lucydom_model
@ -67,14 +66,28 @@ def init_logging():
)
# START APP
# Initialize logging
init_logging()
logger = logging.getLogger(__name__)
instance_label=APP_CONFIG.get("APP_ENV_LABEL")
app = FastAPI(title="PowerOn | Data Platform API", description=f"Backend-API für die Multi-Agent Platform von ValueOn AG ({instance_label})")
instance_label = APP_CONFIG.get("APP_ENV_LABEL")
# CORS-Konfiguration für Frontend-Anfragen
# Define lifespan context manager for application startup/shutdown events
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup logic (if any)
logger.info("Application is starting up")
yield
# Shutdown logic
logger.info("Application has been shut down")
# START APP
app = FastAPI(
title="PowerOn | Data Platform API",
description=f"Backend API for the Multi-Agent Platform by ValueOn AG ({instance_label})",
lifespan=lifespan
)
# CORS configuration for frontend requests
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:8080","https://poweron-lucyagents-xxx.germanywestcentral-01.azurewebsites.net"],
@ -82,10 +95,10 @@ app.add_middleware(
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],
expose_headers=["*"],
max_age=86400 # Erhöhung des Caching für Preflight-Anfragen
max_age=86400 # Increased caching for preflight requests
)
# Statischer Folder für Frontend - mit absolutem Pfad arbeiten
# Static folder for frontend - work with absolute path
base_dir = pathlib.Path(__file__).parent
static_folder = base_dir / "static"
os.makedirs(static_folder, exist_ok=True)
@ -97,11 +110,11 @@ async def favicon():
favicon_path = static_folder / "favicon.ico"
return FileResponse(str(favicon_path))
# Generelle Elements
# General Elements
@app.get("/", tags=["General"])
async def root():
"""API-Statusendpunkt"""
return {"status": "online", "message": "Data Platform API ist aktiv"}
"""API status endpoint"""
return {"status": "online", "message": "Data Platform API is active"}
@app.get("/api/test", tags=["General"])
async def get_test():
@ -112,23 +125,23 @@ async def options_route(full_path: str):
return Response(status_code=200)
# Token-Endpunkt für Login
# Token endpoint for login
@app.post("/api/token", response_model=gateway_model.Token, tags=["General"])
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
# Gateway-Interface ohne Kontext initialisieren
# Initialize Gateway interface without context
gateway = get_gateway_interface()
# Benutzer authentifizieren
# Authenticate user
user = gateway.authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Ungültiger Benutzername oder Passwort",
detail="Invalid username or password",
headers={"WWW-Authenticate": "Bearer"},
)
# Token mit Mandanten-ID erstellen
# Create token with tenant ID
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={
@ -140,12 +153,12 @@ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(
return {"access_token": access_token, "token_type": "bearer"}
# Benutzerinfo abrufen
# Get user info
@app.get("/api/user/me", response_model=Dict[str, Any], tags=["General"])
async def read_user_me(current_user: Dict[str, Any] = Depends(get_current_active_user)):
return current_user
# Alle Router einbinden
# Include all routers
from routes.attributes import router as attributes_router
app.include_router(attributes_router)
@ -164,12 +177,5 @@ app.include_router(prompt_router)
from routes.workflows import router as workflow_router
app.include_router(workflow_router)
# Event handler beim Herunterfahren
@app.on_event("shutdown")
async def shutdown_event():
"""Führt Aufräumarbeiten beim Herunterfahren der Anwendung durch"""
logger.info("Anwendung wurde heruntergefahren")
#if __name__ == "__main__":
# uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)

View file

@ -343,7 +343,6 @@ JSON_OUTPUT = {{
"current_round": 1,
"status": "running",
"last_activity": current_time,
"waiting_for_user": False
}
# Save to database - only the workflow metadata
@ -357,7 +356,6 @@ JSON_OUTPUT = {{
"data_stats": workflow["data_stats"],
"current_round": workflow["current_round"],
"last_activity": workflow["last_activity"],
"waiting_for_user": workflow["waiting_for_user"],
"message_ids": workflow["message_ids"] # Include message_ids
}
self.lucy_interface.create_workflow(workflow_db)
@ -378,7 +376,6 @@ JSON_OUTPUT = {{
# Update status and increment round counter
workflow["status"] = "running"
workflow["last_activity"] = current_time
workflow["waiting_for_user"] = False
# Increment current_round if it exists, otherwise set it to 1
if "current_round" in workflow:
@ -390,7 +387,6 @@ JSON_OUTPUT = {{
workflow_update = {
"status": workflow["status"],
"last_activity": workflow["last_activity"],
"waiting_for_user": workflow["waiting_for_user"],
"current_round": workflow["current_round"]
}
self.lucy_interface.update_workflow(workflow_id, workflow_update)
@ -411,13 +407,11 @@ JSON_OUTPUT = {{
workflow_update = {
"status": "completed",
"last_activity": datetime.now().isoformat(),
"waiting_for_user": True
}
# Update the workflow object in memory
workflow["status"] = workflow_update["status"]
workflow["last_activity"] = workflow_update["last_activity"]
workflow["waiting_for_user"] = workflow_update["waiting_for_user"]
# Save workflow state to database - only relevant fields, not the messages list
self.lucy_interface.update_workflow(workflow["id"], workflow_update)

View file

@ -157,6 +157,7 @@ class AgentWebcrawler(AgentBase):
return plan
else:
# Fallback plan
logger.warning(f"Not able creating research plan, generating fallback plan")
return {
"requires_web_research": True,
"research_questions": ["What information can be found about this topic?"],
@ -304,6 +305,7 @@ class AgentWebcrawler(AgentBase):
result["summary"] = summary
else:
# Fallback if no AI service
logger.warning(f"Not able to summarize result, using fallback plan.")
result["summary"] = f"Content from {result['url']} ({len(content)} characters)"
except Exception as e:

View file

@ -1035,7 +1035,7 @@ class LucyDOMInterface:
"mandate_id": workflow.get("mandate_id", self.mandate_id),
"user_id": workflow.get("user_id", self.user_id),
"name": workflow.get("name", f"Workflow {workflow_id}"),
"status": workflow.get("status", "unknown"),
"status": workflow.get("status", "completed"),
"started_at": workflow.get("started_at", self._get_current_timestamp()),
"last_activity": workflow.get("last_activity", self._get_current_timestamp()),
"data_stats": workflow.get("data_stats", {})

View file

@ -122,7 +122,7 @@ class Workflow(BaseModel):
name: Optional[str] = Field(None, description="Name of the workflow")
mandate_id: int = Field(description="ID of the mandate")
user_id: int = Field(description="ID of the user")
status: str = Field(description="Status of the workflow ('running', 'failed', 'stopped')")
status: str = Field(description="Status of the workflow ('running', 'completed')")
started_at: str = Field(description="Start timestamp")
last_activity: str = Field(description="Timestamp of the last activity")
message_ids: List[str] = Field(default=[], description="List of message IDs in this workflow")

View file

@ -1,13 +1,18 @@
....................... TASKS
can you do following adaptions
can you do following adaptions for the workflow management for the frontend:
- german comments in logs and prompts to translate to english. where to adapt what?
- can you enhance all ai prompts to include, that the output is delivered in the language of the user? Perhaps an option to have a global variable for this, which is also transferred with the task to the agents? Perhaps to do simple ai call with some words to ask AI? I want a solution with minimum impact to the code and simple to use.
- can you check all self.log_add(...) statements and rearrange them. They are for the progress of a workflow to show in the front-end. I want all messages to be in a standardizes format and organized along the workflow, that user understands the logical progress. Not too much information, but the relevant steps to show. Within loops to tell progress in percent by having a log_add in the loops (so to add progress attribute to the function call)
everywhere:
- to remove base64 checks ot tests. only to use base64_encoded attribute
- to use the enhanced attributes for document ("data" containing filedata in base64 format) and content ("data", "base64_encoded", "data_extracted")
topics for log_add log_entry object:
- always to include workflow "status" for frontend polling support
- agent_id to remove
- agent_name to take from a global variable in the according module (same global variables set like global user language)
- add atrribute to show progress
please tell me, where to adapt what in the code. I do not neew fully new code.
please deliver adapted modules when more than 3 parts have to be adapted, otherwise the parts to adapt.
@ -16,16 +21,12 @@ please tell me, where to adapt what in the code. I do not neew fully new code.
german comments in logs and prompts to translate to english. where to adapt what?
can you enhance all ai prompts to include, that the output is delivered in the language of the user?
An option to have a global variable for this, which is also trasferred with the task to the agents?
streamline self.log_add --> to use in a standardized format and to reduce messages to relevant steps
add connector to myoutlook
todo an agent for "code writing and editing" connected to the codebase, working in loops over each document...
FRONTEND:
- General: Adapt to backend changes and simplify polling and frontend objects status, remove unnecessary elements.
- Workflow object has only one attribute for status, this is "status" with value "completed" or "running". All other status objects for workflow to remove.
- polling start/finish and frontend elements status have only to look for "status" value of workflow. especially all the routines for button "stop", "send", animations only rely on this status.
- for log entries to show: check last log-entry and also change in progress attribute of last entry
@ -33,13 +34,17 @@ todo an agent for "code writing and editing" connected to the codebase, working
PRIO1:
sharepoint connector with document search, content search, content extraction
add connector to myoutlook
todo an agent for "code writing and editing" connected to the codebase, working in loops over each document...
sharepoint connector with document search, content search, content extraction
Split big files into content-parts
PRIO2:
implement cleanup routines for files in lucydom_interface (File_Management_CLEANUP_INTERVAL): temp older than interval, all orphaned

View file

@ -5,7 +5,7 @@ from fastapi import status
from modules.auth import get_current_active_user, get_user_context
# Importiere die Attributdefinition und Hilfsfunktionen
from modules.attributes import AttributeDefinition, get_model_attributes
from gateway.modules.def_attributes import AttributeDefinition, get_model_attributes
# Importiere die Modellmodule (ohne spezifische Klassen)
import modules.gateway_model as gateway_model

View file

@ -13,10 +13,10 @@ from modules.configuration import APP_CONFIG
from modules.lucydom_interface import get_lucydom_interface, FileError, FileNotFoundError, FileStorageError, FilePermissionError, FileDeletionError
from modules.lucydom_model import FileItem
# Logger konfigurieren
# Configure logger
logger = logging.getLogger(__name__)
# Alle Attribute des Models ermitteln (außer interne/spezielle Attribute)
# Get all attributes of the model (except internal/special attributes)
def get_model_attributes(model_class):
return [attr for attr in dir(model_class)
if not callable(getattr(model_class, attr))
@ -27,25 +27,25 @@ def get_model_attributes(model_class):
and attr != 'label'
and attr != 'field_labels']
# Modell-Attribute für FileItem
# Model attributes for FileItem
file_attributes = get_model_attributes(FileItem)
@dataclass
class AppContext:
"""Kontext-Objekt für alle benötigten Verbindungen und Benutzerinformationen"""
"""Context object for all required connections and user information"""
mandate_id: int
user_id: int
interface_data: Any # LucyDOM Interface
async def get_context(current_user: Dict[str, Any]) -> AppContext:
"""
Erstellt ein zentrales Kontext-Objekt mit allen benötigten Interfaces
Creates a central context object with all required interfaces
Args:
current_user: Aktueller Benutzer aus der Authentifizierung
current_user: Current user from authentication
Returns:
AppContext-Objekt mit allen benötigten Verbindungen
AppContext object with all required connections
"""
mandate_id, user_id = await get_user_context(current_user)
interface_data = get_lucydom_interface(mandate_id, user_id)
@ -56,7 +56,7 @@ async def get_context(current_user: Dict[str, Any]) -> AppContext:
interface_data=interface_data
)
# Router für Datei-Endpunkte erstellen
# Create router for file endpoints
router = APIRouter(
prefix="/api/files",
tags=["Files"],
@ -71,18 +71,18 @@ router = APIRouter(
@router.get("", response_model=List[Dict[str, Any]])
async def get_files(current_user: Dict[str, Any] = Depends(get_current_active_user)):
"""Alle verfügbaren Dateien abrufen"""
"""Get all available files"""
try:
context = await get_context(current_user)
# Alle Dateien generisch abrufen - nur Metadaten, keine Binärdaten
# Get all files generically - only metadata, no binary data
files = context.interface_data.get_all_files()
return files
except Exception as e:
logger.error(f"Fehler beim Abrufen der Dateien: {str(e)}")
logger.error(f"Error retrieving files: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Fehler beim Abrufen der Dateien: {str(e)}"
detail=f"Error retrieving files: {str(e)}"
)
@ -93,45 +93,45 @@ async def upload_file(
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""
Upload einer Datei
Upload a file
"""
try:
context = await get_context(current_user)
# Datei einlesen
# Read file
file_content = await file.read()
# Größenbeschränkung prüfen
max_size = int(APP_CONFIG.get("File_Management_MAX_UPLOAD_SIZE_MB")) * 1024 * 1024 # in Bytes
# Check size limits
max_size = int(APP_CONFIG.get("File_Management_MAX_UPLOAD_SIZE_MB")) * 1024 * 1024 # in bytes
if len(file_content) > max_size:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail=f"Datei zu groß. Maximale Größe: {APP_CONFIG.get('File_Management_MAX_UPLOAD_SIZE_MB')}MB"
detail=f"File too large. Maximum size: {APP_CONFIG.get('File_Management_MAX_UPLOAD_SIZE_MB')}MB"
)
# Datei über das LucyDOM-Interface in der Datenbank speichern
# Save file via LucyDOM interface in the database
file_meta = context.interface_data.save_uploaded_file(file_content, file.filename)
# Wenn workflow_id angegeben, aktualisiere die Dateiinformationen
# If workflow_id is provided, update the file information
if workflow_id:
update_data = {"workflow_id": workflow_id}
context.interface_data.update_file(file_meta["id"], update_data)
file_meta["workflow_id"] = workflow_id
# Erfolgreiche Antwort
# Successful response
return file_meta
except FileStorageError as e:
logger.error(f"Fehler beim Datei-Upload (Speichern): {str(e)}")
logger.error(f"Error during file upload (storage): {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e)
)
except Exception as e:
logger.error(f"Fehler beim Datei-Upload: {str(e)}")
logger.error(f"Error during file upload: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Fehler beim Datei-Upload: {str(e)}"
detail=f"Error during file upload: {str(e)}"
)
@ -141,17 +141,17 @@ async def get_file(
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""
Gibt eine Datei anhand ihrer ID zum Download zurück.
Ruft sowohl Metadaten als auch Binärdaten ab.
Returns a file by its ID for download.
Retrieves both metadata and binary data.
"""
try:
context = await get_context(current_user)
# Datei über das LucyDOM-Interface aus der Datenbank abrufen
# Verwendet die download_file-Methode, die nun Metadaten und Binärdaten kombiniert
# Get file via LucyDOM interface from the database
# Uses the download_file method, which now combines metadata and binary data
file_data = context.interface_data.download_file(file_id)
# Datei zurückgeben
# Return file
headers = {
"Content-Disposition": f'attachment; filename="{file_data["name"]}"'
}
@ -162,28 +162,28 @@ async def get_file(
)
except FileNotFoundError as e:
logger.warning(f"Datei nicht gefunden: {str(e)}")
logger.warning(f"File not found: {str(e)}")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(e)
)
except FilePermissionError as e:
logger.warning(f"Keine Berechtigung für Datei: {str(e)}")
logger.warning(f"No permission for file: {str(e)}")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=str(e)
)
except FileError as e:
logger.error(f"Fehler beim Abrufen der Datei: {str(e)}")
logger.error(f"Error retrieving file: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e)
)
except Exception as e:
logger.error(f"Unerwarteter Fehler beim Abrufen der Datei: {str(e)}")
logger.error(f"Unexpected error retrieving file: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Fehler beim Abrufen der Datei: {str(e)}"
detail=f"Error retrieving file: {str(e)}"
)
@ -193,42 +193,42 @@ async def delete_file(
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""
Löscht eine Datei anhand ihrer ID aus der Datenbank.
Entfernt sowohl die Metadaten als auch die Binärdaten.
Deletes a file by its ID from the database.
Removes both metadata and binary data.
"""
try:
context = await get_context(current_user)
# Datei über das LucyDOM-Interface löschen
# Die Methode kümmert sich nun um das Löschen beider Tabellen (files und file_data)
# Delete file via LucyDOM interface
# The method now handles deleting from both tables (files and file_data)
context.interface_data.delete_file(file_id)
# Erfolgreiche Löschung ohne Inhalt zurückgeben (204 No Content)
# Return successful deletion without content (204 No Content)
return Response(status_code=status.HTTP_204_NO_CONTENT)
except FileNotFoundError as e:
logger.warning(f"Datei nicht gefunden: {str(e)}")
logger.warning(f"File not found: {str(e)}")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(e)
)
except FilePermissionError as e:
logger.warning(f"Keine Berechtigung zum Löschen der Datei: {str(e)}")
logger.warning(f"No permission to delete file: {str(e)}")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=str(e)
)
except FileDeletionError as e:
logger.error(f"Fehler beim Löschen der Datei: {str(e)}")
logger.error(f"Error deleting file: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e)
)
except Exception as e:
logger.error(f"Unerwarteter Fehler beim Löschen der Datei: {str(e)}")
logger.error(f"Unexpected error deleting file: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Fehler beim Löschen der Datei: {str(e)}"
detail=f"Error deleting file: {str(e)}"
)
@router.get("/stats", response_model=Dict[str, Any])
@ -236,19 +236,19 @@ async def get_file_stats(
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""
Gibt Statistiken über die gespeicherten Dateien zurück.
Returns statistics about the stored files.
"""
try:
context = await get_context(current_user)
# Alle Dateien abrufen - nur Metadaten
# Get all files - metadata only
all_files = context.interface_data.get_all_files()
# Statistiken berechnen
# Calculate statistics
total_files = len(all_files)
total_size = sum(file.get("size", 0) for file in all_files)
# Nach Dateityp gruppieren
# Group by file type
file_types = {}
for file in all_files:
file_type = file.get("mime_type", "unknown").split("/")[0]
@ -263,8 +263,8 @@ async def get_file_stats(
}
except Exception as e:
logger.error(f"Fehler beim Abrufen der Datei-Statistiken: {str(e)}")
logger.error(f"Error retrieving file statistics: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Fehler beim Abrufen der Datei-Statistiken: {str(e)}"
detail=f"Error retrieving file statistics: {str(e)}"
)

View file

@ -11,7 +11,7 @@ from modules.auth import get_current_active_user, get_user_context
from modules.lucydom_interface import get_lucydom_interface
from modules.lucydom_model import Prompt
# Alle Attribute des Models ermitteln (außer interne/spezielle Attribute)
# Get all attributes of the model (except internal/special attributes)
def get_model_attributes(model_class):
return [attr for attr in dir(model_class)
if not callable(getattr(model_class, attr))
@ -22,25 +22,25 @@ def get_model_attributes(model_class):
and attr != 'label'
and attr != 'field_labels']
# Modell-Attribute für Prompt
# Model attributes for Prompt
prompt_attributes = get_model_attributes(Prompt)
@dataclass
class AppContext:
"""Kontext-Objekt für alle benötigten Verbindungen und Benutzerinformationen"""
"""Context object for all required connections and user information"""
mandate_id: int
user_id: int
interface_data: Any # LucyDOM Interface
async def get_context(current_user: Dict[str, Any]) -> AppContext:
"""
Erstellt ein zentrales Kontext-Objekt mit allen benötigten Interfaces
Creates a central context object with all required interfaces
Args:
current_user: Aktueller Benutzer aus der Authentifizierung
current_user: Current user from authentication
Returns:
AppContext-Objekt mit allen benötigten Verbindungen
AppContext object with all required connections
"""
mandate_id, user_id = await get_user_context(current_user)
interface_data = get_lucydom_interface(mandate_id, user_id)
@ -51,7 +51,7 @@ async def get_context(current_user: Dict[str, Any]) -> AppContext:
interface_data=interface_data
)
# Router für Prompt-Endpunkte erstellen
# Create router for prompt endpoints
router = APIRouter(
prefix="/api/prompts",
tags=["Prompts"],
@ -62,10 +62,10 @@ router = APIRouter(
async def get_prompts(
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Alle Prompts abrufen"""
"""Get all prompts"""
context = await get_context(current_user)
# Prompts generisch abrufen
# Retrieve prompts generically
return context.interface_data.get_all_prompts()
@ -74,26 +74,26 @@ async def create_prompt(
prompt: Dict[str, Any] = Body(...),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Einen neuen Prompt erstellen"""
"""Create a new prompt"""
context = await get_context(current_user)
# Attribute aus dem Request dynamisch setzen
# Set attributes from the request dynamically
prompt_data = {}
for attr in prompt_attributes:
if attr in prompt:
prompt_data[attr] = prompt[attr]
# Pflichtfelder mit Standardwerten
# Required fields with default values
content = prompt.get("content", "")
name = prompt.get("name", "Neuer Prompt")
name = prompt.get("name", "New Prompt")
# Prompt erstellen
# Create prompt
new_prompt = context.interface_data.create_prompt(
content=content,
name=name
)
# Aktuelle Zeit für created_at setzen, wenn es im Modell existiert
# Set current time for created_at if it exists in the model
if "created_at" in prompt_attributes and hasattr(new_prompt, "created_at"):
new_prompt["created_at"] = datetime.now().isoformat()
@ -105,15 +105,15 @@ async def get_prompt(
prompt_id: int,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Einen bestimmten Prompt abrufen"""
"""Get a specific prompt"""
context = await get_context(current_user)
# Prompt generisch abrufen
# Get prompt generically
prompt = context.interface_data.get_prompt(prompt_id)
if not prompt:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prompt mit ID {prompt_id} nicht gefunden"
detail=f"Prompt with ID {prompt_id} not found"
)
return prompt
@ -125,28 +125,28 @@ async def update_prompt(
prompt_data: Dict[str, Any] = Body(...),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Einen bestehenden Prompt aktualisieren"""
"""Update an existing prompt"""
context = await get_context(current_user)
# Prüfe, ob der Prompt existiert
# Check if the prompt exists
existing_prompt = context.interface_data.get_prompt(prompt_id)
if not existing_prompt:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prompt mit ID {prompt_id} nicht gefunden"
detail=f"Prompt with ID {prompt_id} not found"
)
# Attribute aus dem Request dynamisch filtern
# Filter attributes from the request dynamically
update_data = {}
for attr in prompt_attributes:
if attr in prompt_data:
update_data[attr] = prompt_data[attr]
# Standardfelder für Update
# Standard fields for update
content = prompt_data.get("content")
name = prompt_data.get("name")
# Prompt aktualisieren
# Update prompt
updated_prompt = context.interface_data.update_prompt(
prompt_id=prompt_id,
content=content,
@ -156,7 +156,7 @@ async def update_prompt(
if not updated_prompt:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Fehler beim Aktualisieren des Prompts"
detail="Error updating the prompt"
)
return updated_prompt
@ -167,22 +167,22 @@ async def delete_prompt(
prompt_id: int,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Einen Prompt löschen"""
"""Delete a prompt"""
context = await get_context(current_user)
# Prüfe, ob der Prompt existiert
# Check if the prompt exists
existing_prompt = context.interface_data.get_prompt(prompt_id)
if not existing_prompt:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prompt mit ID {prompt_id} nicht gefunden"
detail=f"Prompt with ID {prompt_id} not found"
)
success = context.interface_data.delete_prompt(prompt_id)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Fehler beim Löschen des Prompts"
detail="Error deleting the prompt"
)
return {"message": f"Prompt mit ID {prompt_id} wurde erfolgreich gelöscht"}
return {"message": f"Prompt with ID {prompt_id} successfully deleted"}

View file

@ -17,10 +17,10 @@ from modules.chat import get_chat_manager
# Import models
import modules.lucydom_model as lucydom_model
# Logger konfigurieren
# Configure logger
logger = logging.getLogger(__name__)
# Router für Workflow-Endpunkte erstellen
# Create router for workflow endpoints
router = APIRouter(
prefix="/api/workflows",
tags=["Workflow"],
@ -29,7 +29,7 @@ router = APIRouter(
@dataclass
class AppContext:
"""Kontext-Objekt für alle benötigten Verbindungen und Benutzerinformationen"""
"""Context object for all required connections and user information"""
mandate_id: int
user_id: int
interface_data: Any # LucyDOM Interface
@ -37,13 +37,13 @@ class AppContext:
async def get_context(current_user: Dict[str, Any]) -> AppContext:
"""
Erstellt ein zentrales Kontext-Objekt mit allen benötigten Interfaces
Creates a central context object with all required interfaces
Args:
current_user: Aktueller Benutzer aus der Authentifizierung
current_user: Current user from authentication
Returns:
AppContext-Objekt mit allen benötigten Verbindungen
AppContext object with all required connections
"""
mandate_id, user_id = await get_user_context(current_user)
interface_data = get_lucydom_interface(mandate_id, user_id)
@ -59,31 +59,31 @@ async def get_context(current_user: Dict[str, Any]) -> AppContext:
@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"""
"""Lists all workflows of the user"""
context = await get_context(current_user)
# Workflows für den Benutzer abrufen
# Retrieve workflows for the user
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)"),
workflow_id: Optional[str] = Path(None, description="ID of the workflow (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.
Allows the user to send inputs for a running workflow
or start a new workflow.
"""
context = await get_context(current_user)
# Improved logging
logger.info(f"Benutzereingabe für Workflow {workflow_id or 'neu'} empfangen")
logger.info(f"User input for workflow {workflow_id or 'new'} received")
try:
# Workflow mit dem Chat-Manager fortsetzen oder neu starten
# Continue or start workflow with the chat manager
user_input_dict = {
"prompt": user_input.prompt,
"list_file_id": user_input.list_file_id
@ -93,51 +93,51 @@ async def submit_user_input(
if not workflow:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Fehler bei der Verarbeitung der Benutzereingabe"
detail="Error processing user input"
)
return {
"workflow_id": workflow.get("id"),
"status": "processing",
"message": "Benutzereingabe wurde empfangen und wird verarbeitet"
"status": "running",
"message": "User input received and being processed"
}
except HTTPException:
# HTTP-Exceptions weiterleiten
# Forward HTTP exceptions
raise
except Exception as e:
logger.error(f"Fehler bei der Verarbeitung der Benutzereingabe: {str(e)}", exc_info=True)
logger.error(f"Error processing user input: {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)}"
detail=f"Error processing user input: {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"),
workflow_id: str = Path(..., description="ID of the workflow to stop"),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Stoppt einen laufenden Workflow"""
"""Stops a running workflow"""
context = await get_context(current_user)
# Workflow laden
# Load workflow
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"
detail=f"Workflow with ID {workflow_id} not found"
)
# Status auf "stopped" setzen
workflow["status"] = "stopped"
# Set status to "stopped"
workflow["status"] = "completed"
workflow["last_activity"] = datetime.now().isoformat()
# Workflow aktualisieren
# Update workflow
context.interface_data.update_workflow(workflow_id, workflow)
return {
"workflow_id": workflow_id,
"status": "stopped",
"message": "Workflow wurde gestoppt"
"status": "completed",
"message": "Workflow has been stopped"
}
@router.delete("/{workflow_id}", response_model=Dict[str, Any])
@ -145,20 +145,20 @@ async def delete_workflow(
workflow_id: str,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Löscht einen Workflow"""
"""Deletes a workflow"""
context = await get_context(current_user)
# Workflow löschen
# Delete workflow
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"
detail=f"Workflow with ID {workflow_id} not found"
)
return {
"workflow_id": workflow_id,
"message": "Workflow wurde gelöscht"
"message": "Workflow has been deleted"
}
@router.get("/{workflow_id}/data-statistics", response_model=Dict[str, Any])
@ -167,19 +167,19 @@ async def get_workflow_data_statistics(
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""
Gibt Statistiken über die übertragenen Datenmengen für einen Workflow zurück.
Returns statistics about the transferred data volumes for a workflow.
"""
context = await get_context(current_user)
# Workflow laden
# Load workflow
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"
detail=f"Workflow with ID {workflow_id} not found"
)
# Datenstatistiken zurückgeben
# Return data statistics
data_stats = workflow.get("data_stats", {})
if not data_stats:
data_stats = {
@ -199,18 +199,18 @@ async def get_workflow_status(
workflow_id: str,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Status eines Workflows abrufen"""
"""Get the status of a workflow"""
context = await get_context(current_user)
# Workflow laden
# Load workflow
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"
detail=f"Workflow with ID {workflow_id} not found"
)
# Status aus dem geladenen Workflow erstellen
# Create status from the loaded workflow
status_info = {
"id": workflow.get("id"),
"name": workflow.get("name"),
@ -227,20 +227,20 @@ async def get_workflow_logs(
workflow_id: str,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Protokolle eines Workflows abrufen"""
"""Get logs of a workflow"""
context = await get_context(current_user)
# Logs abrufen
# Get logs
logs = context.interface_data.get_workflow_logs(workflow_id)
if not logs:
# Prüfen, ob der Workflow existiert
# Check if the workflow exists
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"
detail=f"Workflow with ID {workflow_id} not found"
)
# Leere Log-Liste zurückgeben
# Return empty log list
logs = []
return logs
@ -250,82 +250,82 @@ async def get_workflow_messages(
workflow_id: str,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Nachrichten eines Workflows abrufen"""
"""Get messages of a workflow"""
context = await get_context(current_user)
# Nachrichten abrufen
# Get messages
messages = context.interface_data.get_workflow_messages(workflow_id)
if messages is None:
# Prüfen, ob der Workflow existiert
# Check if the workflow exists
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"
detail=f"Workflow with ID {workflow_id} not found"
)
# Leere Nachrichtenliste zurückgeben
# Return empty message list
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"),
workflow_id: str = Path(..., description="ID of the workflow"),
message_id: str = Path(..., description="ID of the message to delete"),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""
Löscht eine einzelne Nachricht aus einem Workflow.
Deletes a single message from a workflow.
Diese Funktion entfernt die Nachricht aus dem Workflow und auch aus der Datenbank.
This function removes the message from the workflow and also from the database.
"""
context = await get_context(current_user)
try:
# Prüfen, ob der Workflow existiert
# Check if the workflow exists
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"
detail=f"Workflow with ID {workflow_id} not found"
)
# Nachricht löschen
# Delete message
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"
detail=f"Message with ID {message_id} in workflow {workflow_id} not found"
)
return {
"workflow_id": workflow_id,
"message_id": message_id,
"success": True,
"message": "Nachricht erfolgreich gelöscht"
"message": "Message successfully deleted"
}
except HTTPException:
# Bekannte HTTP-Exceptions weiterleiten
# Forward known HTTP exceptions
raise
except Exception as e:
# Sonstige Fehler abfangen
# Catch other errors
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Fehler beim Löschen der Nachricht: {str(e)}"
detail=f"Error deleting the message: {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"),
workflow_id: str = Path(..., description="ID of the workflow"),
message_id: str = Path(..., description="ID of the message"),
file_id: str = Path(..., description="ID of the file to delete"),
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.
Deletes a single file reference from a message in the workflow.
The file itself is not deleted from the database, only the reference in the message.
"""
context = await get_context(current_user)
@ -333,13 +333,13 @@ async def delete_file_from_message(
logger.debug(f"DELETE request: Remove file {file_id} from message {message_id} in workflow {workflow_id}")
try:
# Datei aus der Nachricht entfernen
# Remove file from the message
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"
detail=f"File with ID {file_id} in message {message_id} not found"
)
return {
@ -347,19 +347,19 @@ async def delete_file_from_message(
"message_id": message_id,
"file_id": file_id,
"success": True,
"message": "Datei erfolgreich aus der Nachricht gelöscht"
"message": "File successfully deleted from message"
}
except HTTPException:
# HTTP-Exceptions weiterleiten
# Forward HTTP exceptions
raise
except Exception as e:
logger.error(f"Fehler beim Löschen der Datei: {str(e)}")
logger.error(f"Error deleting file: {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)}"
detail=f"Error deleting file from message: {str(e)}"
)