diff --git a/gwserver/data_gateway/mandates.json b/gwserver/_database_gateway/mandates.json similarity index 100% rename from gwserver/data_gateway/mandates.json rename to gwserver/_database_gateway/mandates.json diff --git a/gwserver/data_gateway/users.json b/gwserver/_database_gateway/users.json similarity index 100% rename from gwserver/data_gateway/users.json rename to gwserver/_database_gateway/users.json diff --git a/gwserver/data_lucydom/agents.json b/gwserver/_database_lucydom/agents.json similarity index 100% rename from gwserver/data_lucydom/agents.json rename to gwserver/_database_lucydom/agents.json diff --git a/gwserver/data_lucydom/files.json b/gwserver/_database_lucydom/files.json similarity index 100% rename from gwserver/data_lucydom/files.json rename to gwserver/_database_lucydom/files.json diff --git a/gwserver/data_lucydom/prompts.json b/gwserver/_database_lucydom/prompts.json similarity index 100% rename from gwserver/data_lucydom/prompts.json rename to gwserver/_database_lucydom/prompts.json diff --git a/gwserver/data_lucydom/workspaces.json b/gwserver/_database_lucydom/workspaces.json similarity index 100% rename from gwserver/data_lucydom/workspaces.json rename to gwserver/_database_lucydom/workspaces.json diff --git a/gwserver/results/.gitignore b/gwserver/_results/.gitignore similarity index 100% rename from gwserver/results/.gitignore rename to gwserver/_results/.gitignore diff --git a/gwserver/uploads/.gitignore b/gwserver/_uploads/.gitignore similarity index 100% rename from gwserver/uploads/.gitignore rename to gwserver/_uploads/.gitignore diff --git a/gwserver/app.py b/gwserver/app.py index 5f2cff90..2e2f3e9e 100644 --- a/gwserver/app.py +++ b/gwserver/app.py @@ -1,45 +1,38 @@ -from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, Body, Query, status +from fastapi import FastAPI, HTTPException, Depends, Body, status from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import JSONResponse, Response +from fastapi.responses import JSONResponse from fastapi.staticfiles import StaticFiles -from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer +from fastapi.security import OAuth2PasswordRequestForm import uvicorn -from typing import List, Dict, Any, Optional -import uuid +from typing import Dict, Any import os -import json -import asyncio import logging -from datetime import datetime, timedelta +from datetime import timedelta # Import interfaces -from interface_gateway import get_gateway_interface -from interface_lucydom import get_lucydom_interface -from interface_agentservice import get_agent_service +from modules.gateway_interface import get_gateway_interface +from modules.agentservice_interface import get_agent_service # Import auth module from auth import ( create_access_token, - get_current_user, get_current_active_user, get_user_context, ACCESS_TOKEN_EXPIRE_MINUTES ) -# Import models (restructured) -from model_gateway import User, UserInDB, Token, Mandate -from model_lucydom import ( - Workspace, - Agent, - DataObject, - Prompt, - WorkflowRequest, - WorkflowResponse, - LogEntry, - Result -) +# Import models - generisch importieren +import modules.gateway_model as gateway_model +# Import router modules +from routes.attributes import router as attributes_router +from routes.workspace import router as workspace_router +from routes.agent import router as agent_router +from routes.file import router as file_router +from routes.prompt import router as prompt_router +from routes.workflow import router as workflow_router +from routes.mandate import router as mandate_router # Konfiguration des Loggers logging.basicConfig( @@ -60,8 +53,6 @@ app.add_middleware( allow_headers=["*"], ) -# API - ENDPUNKTE - # Statischer Folder für Frontend os.makedirs(os.path.join(os.getcwd(), "static"), exist_ok=True) app.mount("/static", StaticFiles(directory="static"), name="static") @@ -77,7 +68,7 @@ async def get_test(): return "OK 1.0" # Token-Endpunkt für Login -@app.post("/token", response_model=Token, tags=["General"]) +@app.post("/token", response_model=gateway_model.Token, tags=["General"]) async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()): # Gateway-Interface ohne Kontext initialisieren gateway = get_gateway_interface() @@ -106,7 +97,7 @@ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends( # Benutzerregistrierung -@app.post("/api/users/register", response_model=User, tags=["General"]) +@app.post("/api/users/register", response_model=gateway_model.User, tags=["General"]) async def register_user(user_data: dict = Body(...), current_user: Dict[str, Any] = Depends(get_current_active_user)): """Neuen Benutzer registrieren""" # Nur Benutzer des gleichen Mandanten dürfen erstellt werden @@ -137,390 +128,14 @@ async def read_users_me(current_user: Dict[str, Any] = Depends(get_current_activ return current_user -# Workspace-Endpunkte -@app.get("/api/workspaces", tags=["Workspaces"], response_model=List[Dict[str, Any]]) -async def get_workspaces(current_user: Dict[str, Any] = Depends(get_current_active_user)): - """Alle verfügbaren Workspaces abrufen""" - mandate_id, user_id = await get_user_context(current_user) - - # LucyDOM-Interface mit Benutzerkontext initialisieren - lucy_interface = get_lucydom_interface(mandate_id, user_id) - - return lucy_interface.get_all_workspaces() - - -@app.get("/api/workspaces/{workspace_id}", tags=["Workspaces"], response_model=Dict[str, Any]) -async def get_workspace( - workspace_id: int, - current_user: Dict[str, Any] = Depends(get_current_active_user) -): - """Einen bestimmten Workspace mit allen Details abrufen""" - mandate_id, user_id = await get_user_context(current_user) - - # LucyDOM-Interface mit Benutzerkontext initialisieren - lucy_interface = get_lucydom_interface(mandate_id, user_id) - - workspace = lucy_interface.get_workspace(workspace_id) - if not workspace: - raise HTTPException(status_code=404, detail=f"Workspace mit ID {workspace_id} nicht gefunden") - - return workspace - - -@app.post("/api/workspaces", tags=["Workspaces"], response_model=Dict[str, Any]) -async def create_workspace( - workspace: Dict[str, Any] = Body(...), - current_user: Dict[str, Any] = Depends(get_current_active_user) -): - """Einen neuen Workspace erstellen""" - mandate_id, user_id = await get_user_context(current_user) - - # LucyDOM-Interface mit Benutzerkontext initialisieren - lucy_interface = get_lucydom_interface(mandate_id, user_id) - - new_workspace = lucy_interface.create_workspace(name=workspace.get("name", "Neuer Workspace")) - return new_workspace - - -# Agenten-Endpunkte -@app.get("/api/agents", tags=["Agents"], response_model=List[Dict[str, Any]]) -async def get_agents( - workspace_id: Optional[int] = Query(None), - current_user: Dict[str, Any] = Depends(get_current_active_user) -): - """Alle Agenten oder Agenten eines bestimmten Workspaces abrufen""" - mandate_id, user_id = await get_user_context(current_user) - - # LucyDOM-Interface mit Benutzerkontext initialisieren - lucy_interface = get_lucydom_interface(mandate_id, user_id) - - if workspace_id: - return lucy_interface.get_agents_by_workspace(workspace_id) - - return lucy_interface.get_all_agents() - - -@app.post("/api/agents", tags=["Agents"], response_model=Dict[str, Any]) -async def create_agent( - agent: Dict[str, Any] = Body(...), - current_user: Dict[str, Any] = Depends(get_current_active_user) -): - """Einen neuen Agenten erstellen""" - mandate_id, user_id = await get_user_context(current_user) - - # LucyDOM-Interface mit Benutzerkontext initialisieren - lucy_interface = get_lucydom_interface(mandate_id, user_id) - - workspace_id = agent.get("workspace_id") - - if not workspace_id: - raise HTTPException(status_code=400, detail="workspace_id ist erforderlich") - - # Workspace existiert? - workspace = lucy_interface.get_workspace(workspace_id) - if not workspace: - raise HTTPException(status_code=404, detail=f"Workspace mit ID {workspace_id} nicht gefunden") - - new_agent = lucy_interface.create_agent( - name=agent.get("name", "Neuer Agent"), - agent_type=agent.get("type", "generic"), - workspace_id=workspace_id, - capabilities=agent.get("capabilities", []), - description=agent.get("description", "") - ) - - return new_agent - - -# Datei-Endpunkte -@app.get("/api/files", tags=["Files"], 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""" - mandate_id, user_id = await get_user_context(current_user) - - # LucyDOM-Interface mit Benutzerkontext initialisieren - lucy_interface = get_lucydom_interface(mandate_id, user_id) - - return lucy_interface.get_all_files() - - -@app.post("/api/files/upload", tags=["Files"]) -async def upload_file( - file: UploadFile = File(...), - current_user: Dict[str, Any] = Depends(get_current_active_user) -): - """Eine Datei hochladen""" - mandate_id, user_id = await get_user_context(current_user) - - # LucyDOM-Interface mit Benutzerkontext initialisieren - lucy_interface = get_lucydom_interface(mandate_id, user_id) - - try: - # Generiere eine eindeutige ID für die Datei - file_id = str(uuid.uuid4()) - file_ext = os.path.splitext(file.filename)[1] - file_path = os.path.join(get_agent_service(mandate_id, user_id).upload_dir, f"{file_id}{file_ext}") - - # Datei speichern - with open(file_path, "wb") as f: - content = await file.read() - f.write(content) - - # Dateityp bestimmen - file_type = "image" if file.content_type and "image" in file.content_type else "document" - - # In Datenbank speichern - new_file = lucy_interface.create_file( - name=file.filename, - file_type=file_type, - content_type=file.content_type, - size=os.path.getsize(file_path), - path=file_path - ) - - return { - "id": new_file["id"], - "name": new_file["name"], - "type": new_file["type"], - "size": f"{new_file['size'] / (1024 * 1024):.1f} MB", - "upload_date": new_file.get("upload_date") - } - - except Exception as e: - logger.error(f"Fehler beim Hochladen der Datei: {e}") - raise HTTPException(status_code=500, detail=f"Fehler beim Hochladen der Datei: {str(e)}") - - -@app.delete("/api/files/{file_id}", tags=["Files"]) -async def delete_file( - file_id: int, - current_user: Dict[str, Any] = Depends(get_current_active_user) -): - """Löscht eine Datei""" - mandate_id, user_id = await get_user_context(current_user) - - # LucyDOM-Interface mit Benutzerkontext initialisieren - lucy_interface = get_lucydom_interface(mandate_id, user_id) - - try: - # Hole die Datei aus der Datenbank - file = lucy_interface.get_file(file_id) - if not file: - raise HTTPException(status_code=404, detail=f"Datei mit ID {file_id} nicht gefunden") - - # Prüfe, ob die physische Datei existiert - if "path" in file and os.path.exists(file["path"]): - try: - # Versuche, die physische Datei zu löschen - os.remove(file["path"]) - except Exception as e: - logger.warning(f"Konnte physische Datei nicht löschen: {e}") - - # Lösche die Datei aus der Datenbank - success = lucy_interface.delete_file(file_id) - - if not success: - raise HTTPException(status_code=500, detail="Fehler beim Löschen der Datei aus der Datenbank") - - return {"success": True, "message": f"Datei '{file.get('name', 'unbekannt')}' wurde gelöscht"} - - except HTTPException: - raise - except Exception as e: - logger.error(f"Fehler beim Löschen der Datei: {e}") - raise HTTPException(status_code=500, detail=f"Fehler beim Löschen der Datei: {str(e)}") - - -# Prompt-Endpunkte -@app.get("/api/prompts", tags=["Prompts"], response_model=List[Dict[str, Any]]) -async def get_prompts( - workspace_id: Optional[int] = Query(None), - current_user: Dict[str, Any] = Depends(get_current_active_user) -): - """Alle Prompts oder Prompts eines bestimmten Workspaces abrufen""" - mandate_id, user_id = await get_user_context(current_user) - - # LucyDOM-Interface mit Benutzerkontext initialisieren - lucy_interface = get_lucydom_interface(mandate_id, user_id) - - if workspace_id: - return lucy_interface.get_prompts_by_workspace(workspace_id) - - return lucy_interface.get_all_prompts() - - -@app.post("/api/prompts", tags=["Prompts"], response_model=Dict[str, Any]) -async def create_prompt( - prompt: Dict[str, Any] = Body(...), - current_user: Dict[str, Any] = Depends(get_current_active_user) -): - """Einen neuen Prompt erstellen""" - mandate_id, user_id = await get_user_context(current_user) - - # LucyDOM-Interface mit Benutzerkontext initialisieren - lucy_interface = get_lucydom_interface(mandate_id, user_id) - - workspace_id = prompt.get("workspace_id") - - if not workspace_id: - raise HTTPException(status_code=400, detail="workspace_id ist erforderlich") - - # Workspace existiert? - workspace = lucy_interface.get_workspace(workspace_id) - if not workspace: - raise HTTPException(status_code=404, detail=f"Workspace mit ID {workspace_id} nicht gefunden") - - new_prompt = lucy_interface.create_prompt( - content=prompt.get("content", ""), - workspace_id=workspace_id - ) - - return new_prompt - - -# Workflow-Endpunkte -@app.post("/api/workflow/run", tags=["Workflow"], response_model=Dict[str, Any]) -async def run_workflow( - workflow_request: WorkflowRequest = Body(...), - current_user: Dict[str, Any] = Depends(get_current_active_user) -): - """Führt einen Workflow mit den ausgewählten Agenten und Dateien aus""" - mandate_id, user_id = await get_user_context(current_user) - - # LucyDOM-Interface mit Benutzerkontext initialisieren - lucy_interface = get_lucydom_interface(mandate_id, user_id) - - # AgentService mit Benutzerkontext initialisieren - agent_service = get_agent_service(mandate_id, user_id) - - workspace = lucy_interface.get_workspace(workflow_request.workspace_id) - if not workspace: - raise HTTPException(status_code=404, detail=f"Workspace mit ID {workflow_request.workspace_id} nicht gefunden") - - # Prüfen, ob Dateien existieren - files = [] - for file_id in workflow_request.files: - file = lucy_interface.get_file(file_id) - if not file: - raise HTTPException(status_code=404, detail=f"Datei mit ID {file_id} nicht gefunden") - files.append(file) - - # Prüfen, ob Agenten existieren - agents = [] - for agent_id in workflow_request.agents: - agent = lucy_interface.get_agent(agent_id) - if not agent: - raise HTTPException(status_code=404, detail=f"Agent mit ID {agent_id} nicht gefunden") - agents.append(agent) - - # Workflow ID generieren - workflow_id = str(uuid.uuid4()) - - # Workflow starten (asynchron) - workflow_task = asyncio.create_task( - agent_service.execute_workflow( - workflow_id, - workflow_request.prompt, - agents, - files - ) - ) - - # Sofort eine Antwort zurückgeben - return { - "workflow_id": workflow_id, - "status": "running", - "message": "Workflow wurde gestartet" - } - - -@app.get("/api/workflow/{workflow_id}/status", tags=["Workflow"]) -async def get_workflow_status( - workflow_id: str, - current_user: Dict[str, Any] = Depends(get_current_active_user) -): - """Status eines laufenden Workflows abrufen""" - mandate_id, user_id = await get_user_context(current_user) - - # AgentService mit Benutzerkontext initialisieren - agent_service = get_agent_service(mandate_id, user_id) - - status = agent_service.get_workflow_status(workflow_id) - if not status: - raise HTTPException(status_code=404, detail=f"Workflow mit ID {workflow_id} nicht gefunden") - - return status - - -@app.get("/api/workflow/{workflow_id}/logs", tags=["Workflow"], 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""" - mandate_id, user_id = await get_user_context(current_user) - - # AgentService mit Benutzerkontext initialisieren - agent_service = get_agent_service(mandate_id, user_id) - - logs = agent_service.get_workflow_logs(workflow_id) - if logs is None: - raise HTTPException(status_code=404, detail=f"Workflow mit ID {workflow_id} nicht gefunden") - - return logs - - -@app.get("/api/workflow/{workflow_id}/results", tags=["Workflow"], response_model=List[Dict[str, Any]]) -async def get_workflow_results( - workflow_id: str, - current_user: Dict[str, Any] = Depends(get_current_active_user) -): - """Ergebnisse eines Workflows abrufen""" - mandate_id, user_id = await get_user_context(current_user) - - # AgentService mit Benutzerkontext initialisieren - agent_service = get_agent_service(mandate_id, user_id) - - results = agent_service.get_workflow_results(workflow_id) - if results is None: - raise HTTPException(status_code=404, detail=f"Workflow mit ID {workflow_id} nicht gefunden") - - return results - - -# Mandanten-Endpunkte (neu) -@app.get("/api/mandates", tags=["Mandates"], response_model=List[Dict[str, Any]]) -async def get_mandates(current_user: Dict[str, Any] = Depends(get_current_active_user)): - """Alle verfügbaren Mandanten abrufen (nur für Admin-Benutzer)""" - mandate_id, user_id = await get_user_context(current_user) - - # Gateway-Interface mit Benutzerkontext initialisieren - gateway = get_gateway_interface(mandate_id, user_id) - - # TODO: Hier sollte eine Berechtigungsprüfung erfolgen - - return gateway.get_all_mandates() - - -@app.post("/api/mandates", tags=["Mandates"], response_model=Dict[str, Any]) -async def create_mandate( - mandate: Dict[str, Any] = Body(...), - current_user: Dict[str, Any] = Depends(get_current_active_user) -): - """Einen neuen Mandanten erstellen (nur für Admin-Benutzer)""" - mandate_id, user_id = await get_user_context(current_user) - - # Gateway-Interface mit Benutzerkontext initialisieren - gateway = get_gateway_interface(mandate_id, user_id) - - # TODO: Hier sollte eine Berechtigungsprüfung erfolgen - - new_mandate = gateway.create_mandate( - name=mandate.get("name", "Neuer Mandant"), - language=mandate.get("language", "de") - ) - - return new_mandate +# Alle Router einbinden +app.include_router(attributes_router) +app.include_router(workspace_router) +app.include_router(agent_router) +app.include_router(file_router) +app.include_router(prompt_router) +app.include_router(workflow_router) +app.include_router(mandate_router) # Event handler beim Herunterfahren diff --git a/gwserver/attributes.py b/gwserver/attributes.py new file mode 100644 index 00000000..4ccc4e9a --- /dev/null +++ b/gwserver/attributes.py @@ -0,0 +1,113 @@ +from pydantic import BaseModel, Field +from typing import List, Dict, Any, Optional + +# Definiere das Modell für Attributdefinitionen +class AttributeDefinition(BaseModel): + name: str + label: str + type: str + required: bool = False + placeholder: Optional[str] = None + default_value: Optional[Any] = None + options: Optional[List[Dict[str, Any]]] = None + editable: bool = True + visible: bool = True + order: int = 0 + validation: Optional[Dict[str, Any]] = None + help_text: Optional[str] = None + +# Hilfsklassen für Typzuordnung +type_mappings = { + "int": "number", + "str": "string", + "float": "number", + "bool": "boolean", + "List[int]": "array", + "List[str]": "array", + "Dict[str, Any]": "object", + "Optional[str]": "string", + "Optional[int]": "number", + "Optional[Dict[str, Any]]": "object" +} + +# Spezielle Feldtypen basierend auf Namenskonventionen +special_field_types = { + "content": "textarea", + "description": "textarea", + "instructions": "textarea", + "password": "password", + "email": "email", + "workspace_id": "select", + "agent_id": "select", + "type": "select" +} + +# Funktion zum Konvertieren eines Pydantic-Modells in Attributdefinitionen +def get_model_attributes(model_class, user_language="de"): + """ + Konvertiert ein Pydantic-Modell in eine Liste von AttributeDefinition-Objekten + """ + attributes = [] + + # Gehe alle Felder im Modell durch + for i, (field_name, field) in enumerate(model_class.__fields__.items()): + # Überspringe interne Felder + if field_name.startswith('_') or field_name in ["label", "field_labels"]: + continue + + # Bestimme den Feldtyp + field_type = type_mappings.get(str(field.type_), "string") + + # Prüfe auf spezielle Feldtypen + if field_name in special_field_types: + field_type = special_field_types[field_name] + + # Hole das Label (falls vorhanden) + field_label = field_name.replace('_', ' ').capitalize() + if hasattr(model_class, 'field_labels') and field_name in model_class.field_labels: + label_obj = model_class.field_labels[field_name] + field_label = label_obj.get_label(user_language) + + # Standardwerte und Required-Status ermitteln + required = field.required + default_value = field.default if not field.required else None + + # Hinweise auf Validierungsregeln + validation = None + if field.validators: + validation = {"has_validators": True} + + # Platzhaltertext + placeholder = f"Bitte {field_label} eingeben" + + # Spezielle Optionen für Select-Felder + options = None + if field_type == "select": + if field_name == "type" and model_class.__name__ == "Agent": + options = [ + {"value": "Analyse", "label": "Analyse"}, + {"value": "Transformation", "label": "Transformation"}, + {"value": "Generierung", "label": "Generierung"}, + {"value": "Klassifikation", "label": "Klassifikation"}, + {"value": "Benutzerdefiniert", "label": "Benutzerdefiniert"} + ] + + # Attributdefinition erstellen + attr_def = AttributeDefinition( + name=field_name, + label=field_label, + type=field_type, + required=required, + placeholder=placeholder, + default_value=default_value, + options=options, + editable=field_name not in ["id", "mandate_id", "user_id", "created_at", "upload_date"], + visible=field_name not in ["hashed_password", "mandate_id", "user_id"], + order=i, + validation=validation, + help_text=field.description + ) + + attributes.append(attr_def) + + return attributes \ No newline at end of file diff --git a/gwserver/auth.py b/gwserver/auth.py index 8f032a4f..e0238edf 100644 --- a/gwserver/auth.py +++ b/gwserver/auth.py @@ -4,8 +4,8 @@ from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from jose import JWTError, jwt import logging -from interface_gateway import get_gateway_interface -from model_gateway import User, Token +from modules.gateway_interface import get_gateway_interface +from modules.gateway_model import User, Token import configload diff --git a/gwserver/config.ini b/gwserver/config.ini index 9c98fc39..1f0bcfba 100644 --- a/gwserver/config.ini +++ b/gwserver/config.ini @@ -1,7 +1,7 @@ [Application] DEBUG = True -UPLOAD_DIR = ./uploads -RESULTS_DIR = ./results +UPLOAD_DIR = ./_uploads +RESULTS_DIR = ./_results [Access] SECRET_KEY = dein-geheimer-schlüssel diff --git a/gwserver/interface_agentservice.py b/gwserver/modules/agentservice_interface.py similarity index 100% rename from gwserver/interface_agentservice.py rename to gwserver/modules/agentservice_interface.py diff --git a/gwserver/interface_gateway.py b/gwserver/modules/gateway_interface.py similarity index 98% rename from gwserver/interface_gateway.py rename to gwserver/modules/gateway_interface.py index 8fc726e2..4c43b375 100644 --- a/gwserver/interface_gateway.py +++ b/gwserver/modules/gateway_interface.py @@ -4,7 +4,7 @@ from typing import Dict, Any, List, Optional, Union import importlib from passlib.context import CryptContext from connector_db_json import JSONDatabaseConnector -from model_gateway import User, UserInDB, Token, Mandate +from modules.gateway_model import User, UserInDB, Token, Mandate logger = logging.getLogger(__name__) @@ -33,9 +33,9 @@ class GatewayInterface: self.user_id = user_id if user_id is not None else 1 # Admin-Benutzer als Standard # Datenverzeichnis - self.data_folder = "data_gateway" + self.data_folder = "_database_gateway" os.makedirs(self.data_folder, exist_ok=True) - logger.info("db for data_gateway atteached") + logger.info("db for data_gateway attached") # Datenmodell-Modul importieren try: diff --git a/gwserver/model_gateway.py b/gwserver/modules/gateway_model.py similarity index 100% rename from gwserver/model_gateway.py rename to gwserver/modules/gateway_model.py diff --git a/gwserver/interface_lucydom.py b/gwserver/modules/lucydom_interface.py similarity index 99% rename from gwserver/interface_lucydom.py rename to gwserver/modules/lucydom_interface.py index 6f5248dc..0121a3f0 100644 --- a/gwserver/interface_lucydom.py +++ b/gwserver/modules/lucydom_interface.py @@ -26,7 +26,7 @@ class LucyDOMInterface: self.user_id = user_id # Datenverzeichnis - self.data_folder = "data_lucydom" + self.data_folder = "_database_lucydom" os.makedirs(self.data_folder, exist_ok=True) # Datenmodell-Modul importieren diff --git a/gwserver/model_lucydom.py b/gwserver/modules/lucydom_model.py similarity index 100% rename from gwserver/model_lucydom.py rename to gwserver/modules/lucydom_model.py diff --git a/gwserver/routes/agent.py b/gwserver/routes/agent.py new file mode 100644 index 00000000..bb76eb2b --- /dev/null +++ b/gwserver/routes/agent.py @@ -0,0 +1,91 @@ +from fastapi import APIRouter, HTTPException, Depends, Body, Query, Path +from typing import List, Dict, Any, Optional +from fastapi import status + +# Import auth module +from auth import get_current_active_user, get_user_context + +# Import interfaces +from modules.lucydom_interface import get_lucydom_interface + +# Router für Agenten-Endpunkte erstellen +router = APIRouter( + prefix="/api/agents", + tags=["Agents"], + responses={404: {"description": "Not found"}} +) + +@router.get("", response_model=List[Dict[str, Any]]) +async def get_agents( + workspace_id: Optional[int] = Query(None), + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """Alle Agenten oder Agenten eines bestimmten Workspaces abrufen""" + mandate_id, user_id = await get_user_context(current_user) + + # LucyDOM-Interface mit Benutzerkontext initialisieren + lucy_interface = get_lucydom_interface(mandate_id, user_id) + + if workspace_id: + return lucy_interface.get_agents_by_workspace(workspace_id) + + return lucy_interface.get_all_agents() + + +@router.post("", response_model=Dict[str, Any]) +async def create_agent( + agent: Dict[str, Any] = Body(...), + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """Einen neuen Agenten erstellen""" + mandate_id, user_id = await get_user_context(current_user) + + # LucyDOM-Interface mit Benutzerkontext initialisieren + lucy_interface = get_lucydom_interface(mandate_id, user_id) + + workspace_id = agent.get("workspace_id") + + if not workspace_id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="workspace_id ist erforderlich" + ) + + # Workspace existiert? + workspace = lucy_interface.get_workspace(workspace_id) + if not workspace: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Workspace mit ID {workspace_id} nicht gefunden" + ) + + new_agent = lucy_interface.create_agent( + name=agent.get("name", "Neuer Agent"), + agent_type=agent.get("type", "generic"), + workspace_id=workspace_id, + capabilities=agent.get("capabilities", []), + description=agent.get("description", "") + ) + + return new_agent + + +@router.get("/{agent_id}", response_model=Dict[str, Any]) +async def get_agent( + agent_id: int, + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """Einen bestimmten Agenten abrufen""" + mandate_id, user_id = await get_user_context(current_user) + + # LucyDOM-Interface mit Benutzerkontext initialisieren + lucy_interface = get_lucydom_interface(mandate_id, user_id) + + agent = lucy_interface.get_agent(agent_id) + if not agent: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Agent mit ID {agent_id} nicht gefunden" + ) + + return agent \ No newline at end of file diff --git a/gwserver/routes/attributes.py b/gwserver/routes/attributes.py new file mode 100644 index 00000000..7b29112c --- /dev/null +++ b/gwserver/routes/attributes.py @@ -0,0 +1,101 @@ +from fastapi import APIRouter, HTTPException, Depends, Path +from typing import List, Dict, Any +from fastapi import status + +# Import auth module +from auth import get_current_active_user, get_user_context + +# Importiere die Attributdefinition und Hilfsfunktionen +from attributes import AttributeDefinition, get_model_attributes + +# Importiere die Modellmodule (ohne spezifische Klassen) +import modules.gateway_model as gateway_model +import modules.lucydom_model as lucydom_model + +# Erstelle ein Dictionary, das die Modellklassen ihren Typen zuordnet +model_classes = { + "workspace": lucydom_model.Workspace, + "agent": lucydom_model.Agent, + "file": lucydom_model.DataObject, + "prompt": lucydom_model.Prompt, + "user": gateway_model.User, + "mandate": gateway_model.Mandate, + "workflow_request": lucydom_model.WorkflowRequest, + "workflow_response": lucydom_model.WorkflowResponse, + "log_entry": lucydom_model.LogEntry, + "result": lucydom_model.Result +} + +# Erstelle einen Router für die Attribute-Endpunkte +router = APIRouter( + prefix="/api/attributes", + tags=["Attributes"], + responses={404: {"description": "Not found"}} +) + +@router.get("/{entity_type}", response_model=List[AttributeDefinition]) +async def get_entity_attributes( + entity_type: str = Path(..., description="Typ der Entität (z.B. workspace, agent, prompt)"), + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """ + Ruft die Attributdefinitionen für eine bestimmte Entität ab. + Dies kann für die dynamische Generierung von Formularen verwendet werden. + """ + # Authentifizierung und Benutzerkontext + mandate_id, user_id = await get_user_context(current_user) + + # Bevorzugte Sprache des Benutzers ermitteln + user_language = current_user.get("language", "de") + + # Prüfen, ob Entitätstyp bekannt ist + if entity_type not in model_classes: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Entitätstyp '{entity_type}' nicht gefunden." + ) + + # Model-Klasse abrufen und Attribute daraus ableiten + model_class = model_classes[entity_type] + attributes = get_model_attributes(model_class, user_language) + + # Nur bearbeitbare und sichtbare Attribute zurückgeben + visible_attributes = [attr for attr in attributes if attr.visible] + + # Wenn es sich um die Workspace-Auswahl handelt, Daten aus der Datenbank laden + for attr in visible_attributes: + if attr.name == "workspace_id" and attr.type == "select": + # LucyDOM-Interface mit Benutzerkontext initialisieren + from modules.lucydom_interface import get_lucydom_interface + lucy_interface = get_lucydom_interface(mandate_id, user_id) + + # Alle verfügbaren Workspaces abrufen + workspaces = lucy_interface.get_all_workspaces() + + # Options-Liste aus Workspaces erstellen + attr.options = [ + {"value": ws["id"], "label": ws["name"]} + for ws in workspaces + ] + + # Wenn es sich um Agent-Auswahl handelt und ein Workspace-Kontext existiert + elif attr.name == "agent_id" and attr.type == "select" and "workspace_id" in [a.name for a in visible_attributes]: + # Wenn ein aktueller Workspace gesetzt ist + workspace_id_attr = next((a for a in visible_attributes if a.name == "workspace_id"), None) + if workspace_id_attr and workspace_id_attr.default_value: + workspace_id = workspace_id_attr.default_value + + # LucyDOM-Interface mit Benutzerkontext initialisieren + from modules.lucydom_interface import get_lucydom_interface + lucy_interface = get_lucydom_interface(mandate_id, user_id) + + # Agenten für den Workspace abrufen + agents = lucy_interface.get_agents_by_workspace(workspace_id) + + # Options-Liste aus Agenten erstellen + attr.options = [ + {"value": agent["id"], "label": agent["name"]} + for agent in agents + ] + + return visible_attributes \ No newline at end of file diff --git a/gwserver/routes/file.py b/gwserver/routes/file.py new file mode 100644 index 00000000..9893ed1b --- /dev/null +++ b/gwserver/routes/file.py @@ -0,0 +1,132 @@ +from fastapi import APIRouter, HTTPException, Depends, File, UploadFile, Path +from typing import List, Dict, Any +from fastapi import status +import uuid +import os +import logging + +# Import auth module +from auth import get_current_active_user, get_user_context + +# Import interfaces +from modules.lucydom_interface import get_lucydom_interface +from modules.agentservice_interface import get_agent_service + +# Logger konfigurieren +logger = logging.getLogger(__name__) + +# Router für Datei-Endpunkte erstellen +router = APIRouter( + prefix="/api/files", + tags=["Files"], + responses={404: {"description": "Not found"}} +) + +@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""" + mandate_id, user_id = await get_user_context(current_user) + + # LucyDOM-Interface mit Benutzerkontext initialisieren + lucy_interface = get_lucydom_interface(mandate_id, user_id) + + return lucy_interface.get_all_files() + + +@router.post("/upload") +async def upload_file( + file: UploadFile = File(...), + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """Eine Datei hochladen""" + mandate_id, user_id = await get_user_context(current_user) + + # LucyDOM-Interface mit Benutzerkontext initialisieren + lucy_interface = get_lucydom_interface(mandate_id, user_id) + + try: + # Generiere eine eindeutige ID für die Datei + file_id = str(uuid.uuid4()) + file_ext = os.path.splitext(file.filename)[1] + file_path = os.path.join(get_agent_service(mandate_id, user_id).upload_dir, f"{file_id}{file_ext}") + + # Datei speichern + with open(file_path, "wb") as f: + content = await file.read() + f.write(content) + + # Dateityp bestimmen + file_type = "image" if file.content_type and "image" in file.content_type else "document" + + # In Datenbank speichern + new_file = lucy_interface.create_file( + name=file.filename, + file_type=file_type, + content_type=file.content_type, + size=os.path.getsize(file_path), + path=file_path + ) + + return { + "id": new_file["id"], + "name": new_file["name"], + "type": new_file["type"], + "size": f"{new_file['size'] / (1024 * 1024):.1f} MB", + "upload_date": new_file.get("upload_date") + } + + except Exception as e: + logger.error(f"Fehler beim Hochladen der Datei: {e}") + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Fehler beim Hochladen der Datei: {str(e)}" + ) + + +@router.delete("/{file_id}") +async def delete_file( + file_id: int, + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """Löscht eine Datei""" + mandate_id, user_id = await get_user_context(current_user) + + # LucyDOM-Interface mit Benutzerkontext initialisieren + lucy_interface = get_lucydom_interface(mandate_id, user_id) + + try: + # Hole die Datei aus der Datenbank + file = lucy_interface.get_file(file_id) + if not file: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Datei mit ID {file_id} nicht gefunden" + ) + + # Prüfe, ob die physische Datei existiert + if "path" in file and os.path.exists(file["path"]): + try: + # Versuche, die physische Datei zu löschen + os.remove(file["path"]) + except Exception as e: + logger.warning(f"Konnte physische Datei nicht löschen: {e}") + + # Lösche die Datei aus der Datenbank + success = lucy_interface.delete_file(file_id) + + if not success: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Fehler beim Löschen der Datei aus der Datenbank" + ) + + return {"success": True, "message": f"Datei '{file.get('name', 'unbekannt')}' wurde gelöscht"} + + except HTTPException: + raise + except Exception as e: + logger.error(f"Fehler beim Löschen der Datei: {e}") + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Fehler beim Löschen der Datei: {str(e)}" + ) \ No newline at end of file diff --git a/gwserver/routes/mandate.py b/gwserver/routes/mandate.py new file mode 100644 index 00000000..9c5f7b7c --- /dev/null +++ b/gwserver/routes/mandate.py @@ -0,0 +1,73 @@ +from fastapi import APIRouter, HTTPException, Depends, Body +from typing import List, Dict, Any +from fastapi import status + +# Import auth module +from auth import get_current_active_user, get_user_context + +# Import interfaces +from modules.gateway_interface import get_gateway_interface + +# Router für Mandanten-Endpunkte erstellen +router = APIRouter( + prefix="/api/mandates", + tags=["Mandates"], + responses={404: {"description": "Not found"}} +) + +@router.get("", response_model=List[Dict[str, Any]]) +async def get_mandates(current_user: Dict[str, Any] = Depends(get_current_active_user)): + """Alle verfügbaren Mandanten abrufen (nur für Admin-Benutzer)""" + mandate_id, user_id = await get_user_context(current_user) + + # Gateway-Interface mit Benutzerkontext initialisieren + gateway = get_gateway_interface(mandate_id, user_id) + + # TODO: Hier sollte eine Berechtigungsprüfung erfolgen + + return gateway.get_all_mandates() + + +@router.post("", response_model=Dict[str, Any]) +async def create_mandate( + mandate: Dict[str, Any] = Body(...), + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """Einen neuen Mandanten erstellen (nur für Admin-Benutzer)""" + mandate_id, user_id = await get_user_context(current_user) + + # Gateway-Interface mit Benutzerkontext initialisieren + gateway = get_gateway_interface(mandate_id, user_id) + + # TODO: Hier sollte eine Berechtigungsprüfung erfolgen + + new_mandate = gateway.create_mandate( + name=mandate.get("name", "Neuer Mandant"), + language=mandate.get("language", "de") + ) + + return new_mandate + + +@router.get("/{mandate_id}", response_model=Dict[str, Any]) +async def get_mandate( + mandate_id: int, + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """Einen bestimmten Mandanten abrufen""" + user_mandate_id, user_id = await get_user_context(current_user) + + # Gateway-Interface mit Benutzerkontext initialisieren + gateway = get_gateway_interface(user_mandate_id, user_id) + + # TODO: Hier sollte eine Berechtigungsprüfung erfolgen + # Nur Admins oder Benutzer des gleichen Mandanten dürfen den Mandanten sehen + + mandate = gateway.get_mandate(mandate_id) + if not mandate: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Mandant mit ID {mandate_id} nicht gefunden" + ) + + return mandate \ No newline at end of file diff --git a/gwserver/routes/prompt.py b/gwserver/routes/prompt.py new file mode 100644 index 00000000..c069164b --- /dev/null +++ b/gwserver/routes/prompt.py @@ -0,0 +1,88 @@ +from fastapi import APIRouter, HTTPException, Depends, Body, Query, Path +from typing import List, Dict, Any, Optional +from fastapi import status + +# Import auth module +from auth import get_current_active_user, get_user_context + +# Import interfaces +from modules.lucydom_interface import get_lucydom_interface + +# Router für Prompt-Endpunkte erstellen +router = APIRouter( + prefix="/api/prompts", + tags=["Prompts"], + responses={404: {"description": "Not found"}} +) + +@router.get("", response_model=List[Dict[str, Any]]) +async def get_prompts( + workspace_id: Optional[int] = Query(None), + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """Alle Prompts oder Prompts eines bestimmten Workspaces abrufen""" + mandate_id, user_id = await get_user_context(current_user) + + # LucyDOM-Interface mit Benutzerkontext initialisieren + lucy_interface = get_lucydom_interface(mandate_id, user_id) + + if workspace_id: + return lucy_interface.get_prompts_by_workspace(workspace_id) + + return lucy_interface.get_all_prompts() + + +@router.post("", response_model=Dict[str, Any]) +async def create_prompt( + prompt: Dict[str, Any] = Body(...), + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """Einen neuen Prompt erstellen""" + mandate_id, user_id = await get_user_context(current_user) + + # LucyDOM-Interface mit Benutzerkontext initialisieren + lucy_interface = get_lucydom_interface(mandate_id, user_id) + + workspace_id = prompt.get("workspace_id") + + if not workspace_id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="workspace_id ist erforderlich" + ) + + # Workspace existiert? + workspace = lucy_interface.get_workspace(workspace_id) + if not workspace: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Workspace mit ID {workspace_id} nicht gefunden" + ) + + new_prompt = lucy_interface.create_prompt( + content=prompt.get("content", ""), + workspace_id=workspace_id + ) + + return new_prompt + + +@router.get("/{prompt_id}", response_model=Dict[str, Any]) +async def get_prompt( + prompt_id: int, + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """Einen bestimmten Prompt abrufen""" + mandate_id, user_id = await get_user_context(current_user) + + # LucyDOM-Interface mit Benutzerkontext initialisieren + lucy_interface = get_lucydom_interface(mandate_id, user_id) + + prompt = lucy_interface.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" + ) + + return prompt \ No newline at end of file diff --git a/gwserver/routes/workflow.py b/gwserver/routes/workflow.py new file mode 100644 index 00000000..f762e420 --- /dev/null +++ b/gwserver/routes/workflow.py @@ -0,0 +1,148 @@ +from fastapi import APIRouter, HTTPException, Depends, Body, Path +from typing import List, Dict, Any +from fastapi import status +import asyncio +import uuid + +# Import auth module +from auth import get_current_active_user, get_user_context + +# Import interfaces +from modules.lucydom_interface import get_lucydom_interface +from modules.agentservice_interface import get_agent_service + +# Import models +import modules.lucydom_model as lucydom_model + +# Router für Workflow-Endpunkte erstellen +router = APIRouter( + prefix="/api/workflow", + tags=["Workflow"], + responses={404: {"description": "Not found"}} +) + +@router.post("/run", response_model=Dict[str, Any]) +async def run_workflow( + workflow_request: lucydom_model.WorkflowRequest = Body(...), + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """Führt einen Workflow mit den ausgewählten Agenten und Dateien aus""" + mandate_id, user_id = await get_user_context(current_user) + + # LucyDOM-Interface mit Benutzerkontext initialisieren + lucy_interface = get_lucydom_interface(mandate_id, user_id) + + # AgentService mit Benutzerkontext initialisieren + agent_service = get_agent_service(mandate_id, user_id) + + workspace = lucy_interface.get_workspace(workflow_request.workspace_id) + if not workspace: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Workspace mit ID {workflow_request.workspace_id} nicht gefunden" + ) + + # Prüfen, ob Dateien existieren + files = [] + for file_id in workflow_request.files: + file = lucy_interface.get_file(file_id) + if not file: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Datei mit ID {file_id} nicht gefunden" + ) + files.append(file) + + # Prüfen, ob Agenten existieren + agents = [] + for agent_id in workflow_request.agents: + agent = lucy_interface.get_agent(agent_id) + if not agent: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Agent mit ID {agent_id} nicht gefunden" + ) + agents.append(agent) + + # Workflow ID generieren + workflow_id = str(uuid.uuid4()) + + # Workflow starten (asynchron) + workflow_task = asyncio.create_task( + agent_service.execute_workflow( + workflow_id, + workflow_request.prompt, + agents, + files + ) + ) + + # Sofort eine Antwort zurückgeben + return { + "workflow_id": workflow_id, + "status": "running", + "message": "Workflow wurde gestartet" + } + + +@router.get("/{workflow_id}/status") +async def get_workflow_status( + workflow_id: str, + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """Status eines laufenden Workflows abrufen""" + mandate_id, user_id = await get_user_context(current_user) + + # AgentService mit Benutzerkontext initialisieren + agent_service = get_agent_service(mandate_id, user_id) + + status = agent_service.get_workflow_status(workflow_id) + if not status: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Workflow mit ID {workflow_id} nicht gefunden" + ) + + return status + + +@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""" + mandate_id, user_id = await get_user_context(current_user) + + # AgentService mit Benutzerkontext initialisieren + agent_service = get_agent_service(mandate_id, user_id) + + logs = agent_service.get_workflow_logs(workflow_id) + if logs is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Workflow mit ID {workflow_id} nicht gefunden" + ) + + return logs + + +@router.get("/{workflow_id}/results", response_model=List[Dict[str, Any]]) +async def get_workflow_results( + workflow_id: str, + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """Ergebnisse eines Workflows abrufen""" + mandate_id, user_id = await get_user_context(current_user) + + # AgentService mit Benutzerkontext initialisieren + agent_service = get_agent_service(mandate_id, user_id) + + results = agent_service.get_workflow_results(workflow_id) + if results is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Workflow mit ID {workflow_id} nicht gefunden" + ) + + return results \ No newline at end of file diff --git a/gwserver/routes/workspace.py b/gwserver/routes/workspace.py new file mode 100644 index 00000000..c252e190 --- /dev/null +++ b/gwserver/routes/workspace.py @@ -0,0 +1,62 @@ +from fastapi import APIRouter, HTTPException, Depends, Body, Path +from typing import List, Dict, Any +from fastapi import status + +# Import auth module +from auth import get_current_active_user, get_user_context + +# Import interfaces +from modules.lucydom_interface import get_lucydom_interface + +# Router für Workspace-Endpunkte erstellen +router = APIRouter( + prefix="/api/workspaces", + tags=["Workspaces"], + responses={404: {"description": "Not found"}} +) + +@router.get("", response_model=List[Dict[str, Any]]) +async def get_workspaces(current_user: Dict[str, Any] = Depends(get_current_active_user)): + """Alle verfügbaren Workspaces abrufen""" + mandate_id, user_id = await get_user_context(current_user) + + # LucyDOM-Interface mit Benutzerkontext initialisieren + lucy_interface = get_lucydom_interface(mandate_id, user_id) + + return lucy_interface.get_all_workspaces() + + +@router.get("/{workspace_id}", response_model=Dict[str, Any]) +async def get_workspace( + workspace_id: int, + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """Einen bestimmten Workspace mit allen Details abrufen""" + mandate_id, user_id = await get_user_context(current_user) + + # LucyDOM-Interface mit Benutzerkontext initialisieren + lucy_interface = get_lucydom_interface(mandate_id, user_id) + + workspace = lucy_interface.get_workspace(workspace_id) + if not workspace: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Workspace mit ID {workspace_id} nicht gefunden" + ) + + return workspace + + +@router.post("", response_model=Dict[str, Any]) +async def create_workspace( + workspace: Dict[str, Any] = Body(...), + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """Einen neuen Workspace erstellen""" + mandate_id, user_id = await get_user_context(current_user) + + # LucyDOM-Interface mit Benutzerkontext initialisieren + lucy_interface = get_lucydom_interface(mandate_id, user_id) + + new_workspace = lucy_interface.create_workspace(name=workspace.get("name", "Neuer Workspace")) + return new_workspace \ No newline at end of file