From b6ac189abe41c5e5feb1e5142730d5943a8d3053 Mon Sep 17 00:00:00 2001 From: valueon Date: Tue, 18 Mar 2025 23:13:14 +0100 Subject: [PATCH] stable endpoints and connections --- gwserver/_database_lucydom/agents.json | 51 +---- gwserver/_database_lucydom/files.json | 11 -- gwserver/_database_lucydom/prompts.json | 14 -- gwserver/_database_lucydom/workspaces.json | 22 +-- gwserver/app.py | 28 ++- gwserver/{attributes.py => attributes_def.py} | 12 +- gwserver/connector_db_json.py | 37 +++- gwserver/modules/agentservice_interface.py | 2 +- gwserver/modules/gateway_interface.py | 12 +- gwserver/modules/lucydom_interface.py | 182 +++++++++++++++++- gwserver/modules/lucydom_model.py | 2 +- gwserver/routes/agent.py | 77 +++++++- gwserver/routes/attributes.py | 14 +- gwserver/routes/file.py | 4 +- gwserver/routes/workflow.py | 10 +- gwserver/specification.txt | 28 ++- gwserver/static/favicon.ico | Bin 0 -> 3870 bytes readme.md | 5 +- 18 files changed, 386 insertions(+), 125 deletions(-) rename gwserver/{attributes.py => attributes_def.py} (85%) create mode 100644 gwserver/static/favicon.ico diff --git a/gwserver/_database_lucydom/agents.json b/gwserver/_database_lucydom/agents.json index a48dff00..f3ed4c02 100644 --- a/gwserver/_database_lucydom/agents.json +++ b/gwserver/_database_lucydom/agents.json @@ -1,47 +1,12 @@ [ { - "id": "agent_001", - "name": "Datenanalyse-Agent", - "type": "analyzer", - "workspace_id": "workspace_001", - "capabilities": ["Datenanalyse", "Statistik", "Trendanalyse"], - "description": "Spezialisiert auf die Analyse von strukturierten Daten und Informationen.", - "instructions": "Als Datenanalyse-Agent ist es deine Aufgabe, die bereitgestellten Daten zu analysieren und wichtige Erkenntnisse zu extrahieren.\n\nFolge diesen Anweisungen zur Analyse der Dateien:\n1. Lese und verstehe den Inhalt der bereitgestellten Dateien gründlich\n2. Identifiziere welchen Datentyp jede Datei enthält (z.B. Zeitreihendaten, kategorische Daten, Text)\n3. Wenn es sich um tabellarische Daten handelt:\n - Identifiziere Muster, Trends und Anomalien\n - Berechne relevante statistische Kennzahlen (Mittelwerte, Mediane, Standardabweichungen)\n - Suche nach Korrelationen zwischen verschiedenen Spalten\n - Identifiziere Ausreißer und ungewöhnliche Datenpunkte\n4. Wenn es sich um Textdaten handelt:\n - Analysiere Schlüsselthemen und -begriffe\n - Identifiziere Stimmung und Tonalität, wenn relevant\n - Extrahiere zentrale Aussagen und Schlussfolgerungen\n5. Erstelle eine strukturierte Zusammenfassung deiner Erkenntnisse\n6. Gib konkrete, datengestützte Empfehlungen, wenn möglich\n\nIn deiner Antwort:\n- Beginne mit einer kurzen Übersicht der analysierten Daten\n- Strukturiere deine Erkenntnisse klar mit Überschriften und Aufzählungen\n- Füge quantitative Erkenntnisse ein, wo immer möglich\n- Schließe mit einer Zusammenfassung der wichtigsten Punkte ab" - }, - { - "id": "agent_002", - "name": "Visualisierungs-Agent", - "type": "visualizer", - "workspace_id": "workspace_001", - "capabilities": ["Datenvisualisierung", "Diagrammerstellung"], - "description": "Erstellt visuelle Darstellungen aus Daten", - "instructions": "Als Visualisierungs-Agent ist es deine Aufgabe, die Daten visuell zu beschreiben.\n\nFolge diesen Anweisungen:\n1. Analysiere die bereitgestellten Daten und identifiziere die wichtigsten Aspekte für eine Visualisierung\n2. Empfehle geeignete Visualisierungstypen basierend auf der Art der Daten:\n - Für zeitliche Verläufe: Liniendiagramme, Area-Charts\n - Für Kategorienvergleiche: Balkendiagramme, gestapelte Balken\n - Für Verteilungen: Histogramme, Box-Plots\n - Für Anteile: Kreisdiagramme, Donut-Charts\n - Für Beziehungen: Streudiagramme, Heatmaps\n3. Beschreibe detailliert, wie die Visualisierung aufgebaut werden sollte:\n - Welche Daten auf welcher Achse abgebildet werden sollten\n - Farben und ihre Bedeutung\n - Beschriftungen und Legenden\n - Skalen und deren Anpassung\n4. Erkläre, welche Erkenntnisse die Visualisierung vermitteln würde\n5. Gib Empfehlungen für Interaktionsmöglichkeiten oder zusätzliche Visualisierungen" - }, - { - "id": "agent_003", - "name": "Text-Generator", - "type": "writer", - "workspace_id": "workspace_001", - "capabilities": ["Texterstellung", "Zusammenfassung"], - "description": "Verfasst verständliche Berichte und Zusammenfassungen", - "instructions": "Als Text-Generator ist es deine Aufgabe, verständliche Berichte und Zusammenfassungen zu erstellen.\n\nFolge diesen Anweisungen:\n1. Destilliere die wichtigsten Erkenntnisse aus den bereitgestellten Daten und Analysen\n2. Strukturiere den Bericht logisch mit:\n - Einer prägnanten Einleitung mit Kontext und Haupterkenntnissen\n - Einem detaillierten Hauptteil, der die Erkenntnisse systematisch präsentiert\n - Einem Abschluss mit Zusammenfassung und Handlungsempfehlungen\n3. Verwende eine klare, präzise und faktenbasierte Sprache\n4. Integriere Zahlen und Daten direkt in den Text, um Aussagen zu untermauern\n5. Nutze Markdown für eine bessere Formatierung:\n - Überschriften für verschiedene Abschnitte\n - Aufzählungspunkte für Listen\n - Hervorhebungen für wichtige Punkte\n - Tabellen für strukturierte Daten" - }, - { - "id": "agent_004", - "name": "Web-Scraper", - "type": "scraper", - "workspace_id": "workspace_002", - "capabilities": ["Datensammlung", "Webanalyse", "Echtzeit-Recherche"], - "description": "Sammelt und analysiert Daten aus Webquellen in Echtzeit", - "instructions": "Als Web-Scraper-Agent ist es deine Aufgabe, Webseiten zu durchsuchen und relevante Informationen zu extrahieren.\n\nFolge diesen Anweisungen:\n1. Analysiere die bereitgestellten Web-Daten sorgfältig\n2. Extrahiere die wichtigsten Informationen und Fakten aus den Webinhalten\n3. Organisiere die Informationen in klare Kategorien\n4. Identifiziere Trends, Muster und wichtige Erkenntnisse\n5. Vergleiche die Informationen aus verschiedenen Quellen, wenn verfügbar\n6. Fasse die gefundenen Informationen prägnant zusammen\n7. Erkenne mögliche Einschränkungen oder Verzerrungen in den Quellen\n\nIn deiner Antwort:\n- Beginne mit einer Zusammenfassung der gefundenen Informationen\n- Strukturiere die extrahierten Daten in logische Abschnitte\n- Hebe wichtige Fakten und Zahlen hervor\n- Gib Quellenhinweise an\n- Formuliere Schlussfolgerungen basierend auf den gesammelten Daten" - }, - { - "id": "agent_005", - "name": "Marktanalyse-Agent", - "type": "analyzer", - "workspace_id": "workspace_002", - "capabilities": ["Wettbewerbsanalyse", "Markttrends"], - "description": "Spezialisiert auf Wettbewerbsanalyse und Markttrends", - "instructions": "Als Marktanalyse-Agent ist es deine Aufgabe, Wettbewerbsdaten und Markttrends zu analysieren.\n\nFolge diesen Anweisungen:\n1. Identifiziere die wichtigsten Wettbewerber und ihre Positionierung im Markt\n2. Analysiere Markttrends und -entwicklungen:\n - Wachstumsraten und Marktgrößen\n - Veränderungen im Konsumentenverhalten\n - Technologische Entwicklungen\n - Regulatorische Änderungen\n3. Vergleiche Produkte und Dienstleistungen nach relevanten Kriterien\n4. Identifiziere Stärken, Schwächen, Chancen und Risiken (SWOT-Analyse)\n5. Erstelle eine fundierte Einschätzung der Marktposition\n6. Entwickle strategische Empfehlungen basierend auf der Marktanalyse" + "id": 2, + "mandate_id": 1, + "user_id": 1, + "name": "Hugo", + "type": "Transformation", + "workspace_id": 1, + "capabilities": "ccc", + "description": "ddd" } ] \ No newline at end of file diff --git a/gwserver/_database_lucydom/files.json b/gwserver/_database_lucydom/files.json index 79c93337..e69de29b 100644 --- a/gwserver/_database_lucydom/files.json +++ b/gwserver/_database_lucydom/files.json @@ -1,11 +0,0 @@ -[ - { - "id": "0399d060-5beb-46e6-9c3c-830ae7a471cb", - "name": "Index.pdf", - "type": "document", - "path": "D:\\Athi\\Local\\Web\\git-apps\\gateway\\gwserver\\uploads\\0399d060-5beb-46e6-9c3c-830ae7a471cb.pdf", - "content_type": "application/pdf", - "size": 114510, - "upload_date": "2025-03-16T02:10:23.229879" - } -] \ No newline at end of file diff --git a/gwserver/_database_lucydom/prompts.json b/gwserver/_database_lucydom/prompts.json index 4d0dafda..e69de29b 100644 --- a/gwserver/_database_lucydom/prompts.json +++ b/gwserver/_database_lucydom/prompts.json @@ -1,14 +0,0 @@ -[ - { - "id": "prompt_001", - "content": "Analysiere die Quartalsdaten und erstelle eine Zusammenfassung mit wichtigsten Trends", - "workspace_id": "workspace_001", - "created_at": "2025-03-14T00:25:25.076526" - }, - { - "id": "prompt_002", - "content": "Erstelle eine Prognose für das nächste Quartal basierend auf historischen Daten", - "workspace_id": "workspace_001", - "created_at": "2025-03-14T00:25:25.076526" - } -] \ No newline at end of file diff --git a/gwserver/_database_lucydom/workspaces.json b/gwserver/_database_lucydom/workspaces.json index c477adb1..f51bab46 100644 --- a/gwserver/_database_lucydom/workspaces.json +++ b/gwserver/_database_lucydom/workspaces.json @@ -1,18 +1,16 @@ [ { - "id": "workspace_001", - "name": "Datenanalyse-Projekt", - "created_at": "2025-03-14T00:25:25.076526", - "prompts": [], - "agents": [], - "dataObjectReferences": [] + "id": 1, + "mandate_id": 1, + "user_id": 1, + "name": "Default Workspace", + "created_at": "2025-03-17T18:14:50.796803" }, { - "id": "workspace_002", - "name": "Marktforschung", - "created_at": "2025-03-14T00:25:25.076526", - "prompts": [], - "agents": [], - "dataObjectReferences": [] + "id": 1, + "mandate_id": 0, + "user_id": 0, + "name": "Default Workspace", + "created_at": "2025-03-17T22:25:11.350744" } ] \ No newline at end of file diff --git a/gwserver/app.py b/gwserver/app.py index 2e2f3e9e..a283e127 100644 --- a/gwserver/app.py +++ b/gwserver/app.py @@ -1,6 +1,6 @@ -from fastapi import FastAPI, HTTPException, Depends, Body, status +from fastapi import FastAPI, HTTPException, Depends, Body, status, Response from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import JSONResponse +from fastapi.responses import JSONResponse, FileResponse from fastapi.staticfiles import StaticFiles from fastapi.security import OAuth2PasswordRequestForm @@ -12,7 +12,8 @@ from datetime import timedelta # Import interfaces from modules.gateway_interface import get_gateway_interface -from modules.agentservice_interface import get_agent_service +from modules.lucydom_interface import get_lucydom_interface +from modules.agentservice_interface import get_agentservice_interface # Import auth module from auth import ( @@ -47,16 +48,23 @@ app = FastAPI(title="PowerOn | Data Platform API", description="Backend-API für # CORS-Konfiguration für Frontend-Anfragen app.add_middleware( CORSMiddleware, - allow_origins=["http://localhost:8080","https://poweron-gateway-fsahaxgbfee8djea.germanywestcentral-01.azurewebsites.net"], + allow_origins=["http://localhost:8080","https://poweron-lucyagents-xxx.germanywestcentral-01.azurewebsites.net"], allow_credentials=True, - allow_methods=["*"], + allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], allow_headers=["*"], + expose_headers=["*"], + max_age=86400 # Erhöhung des Caching für Preflight-Anfragen ) # Statischer Folder für Frontend os.makedirs(os.path.join(os.getcwd(), "static"), exist_ok=True) app.mount("/static", StaticFiles(directory="static"), name="static") +# Add a specific route for favicon.ico +@app.get("/favicon.ico", include_in_schema=False) +async def favicon(): + return FileResponse("static/favicon.ico") + # Generelle Elements @app.get("/", tags=["General"]) async def root(): @@ -65,10 +73,14 @@ async def root(): @app.get("/api/test", tags=["General"]) async def get_test(): - return "OK 1.0" + return "OK 1.1" + +@app.options("/{full_path:path}") +async def options_route(full_path: str): + return Response(status_code=200) # Token-Endpunkt für Login -@app.post("/token", response_model=gateway_model.Token, tags=["General"]) +@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 gateway = get_gateway_interface() @@ -143,7 +155,7 @@ app.include_router(mandate_router) async def shutdown_event(): """Führt Aufräumarbeiten beim Herunterfahren der Anwendung durch""" # Holen des AgentService ohne Kontext (für Aufräumarbeiten) - agent_service = get_agent_service() + agent_service = get_agentservice_interface() # HTTP-Client des AgentService schließen await agent_service.close() diff --git a/gwserver/attributes.py b/gwserver/attributes_def.py similarity index 85% rename from gwserver/attributes.py rename to gwserver/attributes_def.py index 4ccc4e9a..b3eac248 100644 --- a/gwserver/attributes.py +++ b/gwserver/attributes_def.py @@ -91,6 +91,16 @@ def get_model_attributes(model_class, user_language="de"): {"value": "Klassifikation", "label": "Klassifikation"}, {"value": "Benutzerdefiniert", "label": "Benutzerdefiniert"} ] + + # Extrahiere die Beschreibung aus dem Field-Objekt + description = None + # Versuche, die description aus verschiedenen möglichen Quellen zu holen + if hasattr(field, 'field_info') and hasattr(field.field_info, 'description'): + description = field.field_info.description + elif hasattr(field, 'description'): + description = field.description + elif hasattr(field, 'schema') and hasattr(field.schema, 'description'): + description = field.schema.description # Attributdefinition erstellen attr_def = AttributeDefinition( @@ -105,7 +115,7 @@ def get_model_attributes(model_class, user_language="de"): visible=field_name not in ["hashed_password", "mandate_id", "user_id"], order=i, validation=validation, - help_text=field.description + help_text=description or "" # Setze leeren String als Standardwert, wenn keine Beschreibung gefunden wurde ) attributes.append(attr_def) diff --git a/gwserver/connector_db_json.py b/gwserver/connector_db_json.py index a152029c..53dd6baa 100644 --- a/gwserver/connector_db_json.py +++ b/gwserver/connector_db_json.py @@ -97,7 +97,7 @@ class JSONDatabaseConnector: sofern diese Felder im Datensatz existieren. """ filtered_records = [] - + for record in records: # Prüfe, ob mandate_id im Datensatz existiert und nicht null ist has_mandate = "mandate_id" in record and record["mandate_id"] is not None and record["mandate_id"] != "" @@ -107,21 +107,24 @@ class JSONDatabaseConnector: # Wenn beides existiert, filtere entsprechend if has_mandate and has_user: + print(" DEBUG opt m+u: record +mandate:",record["mandate_id"]) if record["mandate_id"] == self.mandate_id: filtered_records.append(record) # Wenn nur mandate_id existiert elif has_mandate and not has_user: + print(" DEBUG opt m : record +mandate:",record["mandate_id"]) if record["mandate_id"] == self.mandate_id: filtered_records.append(record) # Wenn weder mandate_id noch user_id existieren, füge den Datensatz hinzu elif not has_mandate and not has_user: + print(" DEBUG opt ---: record +") filtered_records.append(record) return filtered_records - def _apply_field_filter(self, records: List[Dict[str, Any]], field_filter: Dict[str, Any] = None) -> List[Dict[str, Any]]: - """Wendet einen Feldfilter auf die Datensätze an""" - if not field_filter: + def _apply_record_filter(self, records: List[Dict[str, Any]], record_filter: Dict[str, Any] = None) -> List[Dict[str, Any]]: + """Wendet einen Datensatzfilter auf die Datensätze an""" + if not record_filter: return records filtered_records = [] @@ -129,16 +132,32 @@ class JSONDatabaseConnector: for record in records: match = True - for field, value in field_filter.items(): - if field not in record or record[field] != value: + for field, value in record_filter.items(): + # Prüfen, ob das Feld existiert + if field not in record: + print(" - field not in record") + match = False + break + + # Wenn der Filterwert ein Integer-String ist und das Datensatzfeld ein Integer + if isinstance(value, str) and value.isdigit() and isinstance(record[field], int): + if record[field] != int(value): + print(" - ",record[field],"!=",int(value)) + match = False + break + # Sonst direkter Vergleich + elif record[field] != value: + print(" - ",record[field],"!='",value,"'") match = False break if match: + print(" + take it") filtered_records.append(record) return filtered_records - + + # Public API def get_tables(self, filter_criteria: Dict[str, Any] = None) -> List[str]: @@ -239,13 +258,13 @@ class JSONDatabaseConnector: """ # Lade die Tabellendaten data = self._load_table(table) - + # Filtere nach Mandanten- und Benutzerkontext filtered_data = self._filter_by_context(data) # Wende record_filter an, wenn vorhanden if record_filter: - filtered_data = self._apply_field_filter(filtered_data, record_filter) + filtered_data = self._apply_record_filter(filtered_data, record_filter) # Wenn field_filter vorhanden ist, reduziere die Felder if field_filter and isinstance(field_filter, list): diff --git a/gwserver/modules/agentservice_interface.py b/gwserver/modules/agentservice_interface.py index 77656f8c..764a9438 100644 --- a/gwserver/modules/agentservice_interface.py +++ b/gwserver/modules/agentservice_interface.py @@ -732,7 +732,7 @@ class AgentService: # Singleton-Factory für AgentService-Instanzen pro Kontext _agent_service_instances = {} -def get_agent_service(mandate_id: int = None, user_id: int = None) -> AgentService: +def get_agentservice_interface(mandate_id: int = None, user_id: int = None) -> AgentService: """ Gibt eine AgentService-Instanz für den angegebenen Kontext zurück. Wiederverwendet bestehende Instanzen. diff --git a/gwserver/modules/gateway_interface.py b/gwserver/modules/gateway_interface.py index 4c43b375..d6978d44 100644 --- a/gwserver/modules/gateway_interface.py +++ b/gwserver/modules/gateway_interface.py @@ -39,10 +39,10 @@ class GatewayInterface: # Datenmodell-Modul importieren try: - self.model_module = importlib.import_module("model_gateway") - logger.info("model_gateway erfolgreich importiert") + self.model_module = importlib.import_module("modules.gateway_model") + logger.info("gateway_model erfolgreich importiert") except ImportError as e: - logger.error(f"Fehler beim Importieren von model_gateway: {e}") + logger.error(f"Fehler beim Importieren von gateway_model: {e}") raise # Konnektor erstellen @@ -251,4 +251,8 @@ def get_gateway_interface(mandate_id: int = None, user_id: int = None) -> Gatewa context_key = f"{mandate_id}_{user_id}" if context_key not in _gateway_interfaces: _gateway_interfaces[context_key] = GatewayInterface(mandate_id, user_id) - return _gateway_interfaces[context_key] \ No newline at end of file + return _gateway_interfaces[context_key] + +#Init +get_gateway_interface() + diff --git a/gwserver/modules/lucydom_interface.py b/gwserver/modules/lucydom_interface.py index 0121a3f0..113b00e7 100644 --- a/gwserver/modules/lucydom_interface.py +++ b/gwserver/modules/lucydom_interface.py @@ -31,10 +31,10 @@ class LucyDOMInterface: # Datenmodell-Modul importieren try: - self.model_module = importlib.import_module("model_lucydom") - logger.info("model_lucydom erfolgreich importiert") + self.model_module = importlib.import_module("modules.lucydom_model") + logger.info("lucydom_model erfolgreich importiert") except ImportError as e: - logger.error(f"Fehler beim Importieren von model_lucydom: {e}") + logger.error(f"Fehler beim Importieren von lucydom_model: {e}") raise # Konnektor erstellen @@ -107,6 +107,42 @@ class LucyDOMInterface: return self.db.record_create("workspaces", workspace_data) + def update_workspace(self, workspace_id: int, name: str) -> Dict[str, Any]: + """ + Aktualisiert einen vorhandenen Workspace + + Args: + workspace_id: ID des zu aktualisierenden Workspaces + name: Neuer Name des Workspaces + + Returns: + Das aktualisierte Workspace-Objekt + """ + # Prüfen, ob der Workspace existiert + workspace = self.get_workspace(workspace_id) + if not workspace: + return None + + # Daten für die Aktualisierung vorbereiten + workspace_data = { + "id": workspace_id, + "mandate_id": self.mandate_id, + "user_id": self.user_id, + "name": name, + "created_at": workspace.get("created_at") + } + + # Workspace aktualisieren + return self.db.record_modify("workspaces", workspace_id, workspace_data) + + def delete_workspace(self, workspace_id: int) -> bool: + """ + Löscht einen Workspace aus der Datenbank + Returns: + True, wenn der Workspace erfolgreich gelöscht wurde, sonst False + """ + return self.db.record_delete("workspaces", workspace_id) + # Agent-Methoden def get_all_agents(self) -> List[Dict[str, Any]]: @@ -125,7 +161,7 @@ class LucyDOMInterface: return None def create_agent(self, name: str, agent_type: str, workspace_id: int, - capabilities: List[str] = None, description: str = None) -> Dict[str, Any]: + capabilities: str = None, description: str = None) -> Dict[str, Any]: """Erstellt einen neuen Agenten""" # Bestimme die nächste ID agents = self.db.get_recordset("agents") @@ -140,11 +176,61 @@ class LucyDOMInterface: "name": name, "type": agent_type, "workspace_id": workspace_id, - "capabilities": capabilities or [], + "capabilities": capabilities, "description": description } return self.db.record_create("agents", agent_data) + + def update_agent(self, agent_id: int, name: str, agent_type: str, workspace_id: int, + capabilities: str = None, description: str = None) -> Dict[str, Any]: + """ + Aktualisiert einen vorhandenen Agenten + + Args: + agent_id: ID des zu aktualisierenden Agenten + name: Neuer Name des Agenten + agent_type: Neuer Typ des Agenten + workspace_id: ID des Workspaces, zu dem der Agent gehört + capabilities: Fähigkeiten des Agenten + description: Beschreibung des Agenten + + Returns: + Das aktualisierte Agenten-Objekt + """ + # Prüfen, ob der Agent existiert + agent = self.get_agent(agent_id) + if not agent: + return None + + # Daten für die Aktualisierung vorbereiten + agent_data = { + "id": agent_id, + "mandate_id": self.mandate_id, + "user_id": self.user_id, + "name": name, + "type": agent_type, + "workspace_id": workspace_id, + "capabilities": capabilities if capabilities is not None else agent.get("capabilities"), + "description": description if description is not None else agent.get("description") + } + + # Agent aktualisieren + updated_agent = self.db.record_modify("agents", agent_id, agent_data) + + return updated_agent + + def delete_agent(self, agent_id: int) -> bool: + """ + Löscht einen Agenten aus der Datenbank + + Args: + agent_id: ID des zu löschenden Agenten + + Returns: + True, wenn der Agent erfolgreich gelöscht wurde, sonst False + """ + return self.db.record_delete("agents", agent_id) # Datei-Methoden @@ -182,6 +268,43 @@ class LucyDOMInterface: return self.db.record_create("files", file_data) + def update_file(self, file_id: int, name: str = None, file_type: str = None, + content_type: str = None, size: int = None, path: str = None) -> Dict[str, Any]: + """ + Aktualisiert eine vorhandene Datei + + Args: + file_id: ID der zu aktualisierenden Datei + name: Neuer Name der Datei + file_type: Neuer Dateityp + content_type: Neuer Content-Type + size: Neue Dateigröße + path: Neuer Dateipfad + + Returns: + Das aktualisierte Datei-Objekt + """ + # Prüfen, ob die Datei existiert + file = self.get_file(file_id) + if not file: + return None + + # Daten für die Aktualisierung vorbereiten + file_data = { + "id": file_id, + "mandate_id": self.mandate_id, + "user_id": self.user_id, + "name": name if name is not None else file.get("name"), + "type": file_type if file_type is not None else file.get("type"), + "content_type": content_type if content_type is not None else file.get("content_type"), + "size": size if size is not None else file.get("size"), + "path": path if path is not None else file.get("path"), + "upload_date": file.get("upload_date") + } + + # Datei aktualisieren + return self.db.record_modify("files", file_id, file_data) + def delete_file(self, file_id: int) -> bool: """Löscht eine Datei aus der Datenbank""" return self.db.record_delete("files", file_id) @@ -221,12 +344,54 @@ class LucyDOMInterface: } return self.db.record_create("prompts", prompt_data) + + def update_prompt(self, prompt_id: int, content: str = None, workspace_id: int = None) -> Dict[str, Any]: + """ + Aktualisiert einen vorhandenen Prompt + + Args: + prompt_id: ID des zu aktualisierenden Prompts + content: Neuer Inhalt des Prompts + workspace_id: Neue ID des Workspaces, zu dem der Prompt gehört + + Returns: + Das aktualisierte Prompt-Objekt + """ + # Prüfen, ob der Prompt existiert + prompt = self.get_prompt(prompt_id) + if not prompt: + return None + + # Daten für die Aktualisierung vorbereiten + prompt_data = { + "id": prompt_id, + "mandate_id": self.mandate_id, + "user_id": self.user_id, + "content": content if content is not None else prompt.get("content"), + "workspace_id": workspace_id if workspace_id is not None else prompt.get("workspace_id"), + "created_at": prompt.get("created_at") + } + + # Prompt aktualisieren + return self.db.record_modify("prompts", prompt_id, prompt_data) + + def delete_prompt(self, prompt_id: int) -> bool: + """ + Löscht einen Prompt aus der Datenbank + + Args: + prompt_id: ID des zu löschenden Prompts + + Returns: + True, wenn der Prompt erfolgreich gelöscht wurde, sonst False + """ + return self.db.record_delete("prompts", prompt_id) # Singleton-Factory für LucyDOMInterface-Instanzen pro Kontext _lucydom_interfaces = {} -def get_lucydom_interface(mandate_id: int, user_id: int) -> LucyDOMInterface: +def get_lucydom_interface(mandate_id: int = 0, user_id: int = 0) -> LucyDOMInterface: """ Gibt eine LucyDOMInterface-Instanz für den angegebenen Kontext zurück. Wiederverwendet bestehende Instanzen. @@ -234,4 +399,7 @@ def get_lucydom_interface(mandate_id: int, user_id: int) -> LucyDOMInterface: context_key = f"{mandate_id}_{user_id}" if context_key not in _lucydom_interfaces: _lucydom_interfaces[context_key] = LucyDOMInterface(mandate_id, user_id) - return _lucydom_interfaces[context_key] \ No newline at end of file + return _lucydom_interfaces[context_key] + +#Init +get_lucydom_interface() \ No newline at end of file diff --git a/gwserver/modules/lucydom_model.py b/gwserver/modules/lucydom_model.py index e76d44fc..945421d4 100644 --- a/gwserver/modules/lucydom_model.py +++ b/gwserver/modules/lucydom_model.py @@ -23,7 +23,7 @@ class Agent(BaseModel): name: str = Field(description="Name des Agenten") type: str = Field(description="Typ des Agenten") workspace_id: int = Field(description="ID des zugehörigen Workspaces") - capabilities: List[str] = Field(default=[], description="Fähigkeiten des Agenten") + capabilities: Optional[str] = Field(None, description="Fähigkeiten des Agenten") description: Optional[str] = Field(None, description="Beschreibung des Agenten") instructions: Optional[str] = Field(None, description="Anweisungen für den Agenten") diff --git a/gwserver/routes/agent.py b/gwserver/routes/agent.py index bb76eb2b..5c391801 100644 --- a/gwserver/routes/agent.py +++ b/gwserver/routes/agent.py @@ -56,14 +56,14 @@ async def create_agent( if not workspace: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, - detail=f"Workspace mit ID {workspace_id} nicht gefunden" + detail=f"Workspace mit ID {workspace_id} nicht gefunden für User {user_id} im Mandanten {mandate_id}" ) 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", []), + capabilities=agent.get("capabilities", ""), description=agent.get("description", "") ) @@ -83,9 +83,80 @@ async def get_agent( 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 + + +@router.post("/{agent_id}", response_model=Dict[str, Any]) +async def update_agent( + agent_id: int = Path(..., description="ID des zu aktualisierenden Agenten"), + agent_data: Dict[str, Any] = Body(..., description="Aktualisierte Agentendaten"), + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """Einen bestehenden Agenten aktualisieren""" + mandate_id, user_id = await get_user_context(current_user) + + # LucyDOM-Interface mit Benutzerkontext initialisieren + lucy_interface = get_lucydom_interface(mandate_id, user_id) + + # Überprüfen, ob der Agent existiert + existing_agent = lucy_interface.get_agent(agent_id) + if not existing_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 + # Wenn workspace_id im Request vorhanden ist, prüfen ob der Workspace existiert + if "workspace_id" in agent_data: + workspace_id = agent_data["workspace_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 für User {user_id} im Mandanten {mandate_id}" + ) + + # Agent-Daten aktualisieren + updated_agent = lucy_interface.update_agent( + agent_id=agent_id, + name=agent_data.get("name", existing_agent.get("name")), + agent_type=agent_data.get("type", existing_agent.get("type")), + workspace_id=agent_data.get("workspace_id", existing_agent.get("workspace_id")), + capabilities=agent_data.get("capabilities", existing_agent.get("capabilities")), + description=agent_data.get("description", existing_agent.get("description")) + ) + return updated_agent + +@router.delete("/{agent_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_agent( + agent_id: int = Path(..., description="ID des zu löschenden Agenten"), + current_user: Dict[str, Any] = Depends(get_current_active_user) +): + """Einen Agenten löschen""" + mandate_id, user_id = await get_user_context(current_user) + + # LucyDOM-Interface mit Benutzerkontext initialisieren + lucy_interface = get_lucydom_interface(mandate_id, user_id) + + # Überprüfen, ob der Agent existiert + existing_agent = lucy_interface.get_agent(agent_id) + if not existing_agent: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Agent mit ID {agent_id} nicht gefunden" + ) + + # Agent löschen + success = lucy_interface.delete_agent(agent_id) + if not success: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Fehler beim Löschen des Agenten mit ID {agent_id}" + ) + + # Kein Inhalt zurückgeben bei erfolgreichem Löschen + return None \ No newline at end of file diff --git a/gwserver/routes/attributes.py b/gwserver/routes/attributes.py index 7b29112c..2544161d 100644 --- a/gwserver/routes/attributes.py +++ b/gwserver/routes/attributes.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, HTTPException, Depends, Path +from fastapi import APIRouter, HTTPException, Depends, Path, Response from typing import List, Dict, Any from fastapi import status @@ -6,7 +6,7 @@ from fastapi import status from auth import get_current_active_user, get_user_context # Importiere die Attributdefinition und Hilfsfunktionen -from attributes import AttributeDefinition, get_model_attributes +from attributes_def import AttributeDefinition, get_model_attributes # Importiere die Modellmodule (ohne spezifische Klassen) import modules.gateway_model as gateway_model @@ -33,6 +33,16 @@ router = APIRouter( responses={404: {"description": "Not found"}} ) +@router.get("/test", response_model=str) +async def get_entity_test(s:str): + return s+s + +@router.options("/{entity_type}") +async def options_entity_attributes( + entity_type: str = Path(..., description="Typ der Entität (z.B. workspace, agent, prompt)") +): + return Response(status_code=200) + @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)"), diff --git a/gwserver/routes/file.py b/gwserver/routes/file.py index 9893ed1b..f8e05574 100644 --- a/gwserver/routes/file.py +++ b/gwserver/routes/file.py @@ -10,7 +10,7 @@ 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 +from modules.agentservice_interface import get_agentservice_interface # Logger konfigurieren logger = logging.getLogger(__name__) @@ -48,7 +48,7 @@ async def upload_file( # 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}") + file_path = os.path.join(get_agentservice_interface(mandate_id, user_id).upload_dir, f"{file_id}{file_ext}") # Datei speichern with open(file_path, "wb") as f: diff --git a/gwserver/routes/workflow.py b/gwserver/routes/workflow.py index f762e420..46cf072b 100644 --- a/gwserver/routes/workflow.py +++ b/gwserver/routes/workflow.py @@ -9,7 +9,7 @@ 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 +from modules.agentservice_interface import get_agentservice_interface # Import models import modules.lucydom_model as lucydom_model @@ -33,7 +33,7 @@ async def run_workflow( lucy_interface = get_lucydom_interface(mandate_id, user_id) # AgentService mit Benutzerkontext initialisieren - agent_service = get_agent_service(mandate_id, user_id) + agent_service = get_agentservice_interface(mandate_id, user_id) workspace = lucy_interface.get_workspace(workflow_request.workspace_id) if not workspace: @@ -94,7 +94,7 @@ async def get_workflow_status( mandate_id, user_id = await get_user_context(current_user) # AgentService mit Benutzerkontext initialisieren - agent_service = get_agent_service(mandate_id, user_id) + agent_service = get_agentservice_interface(mandate_id, user_id) status = agent_service.get_workflow_status(workflow_id) if not status: @@ -115,7 +115,7 @@ async def get_workflow_logs( mandate_id, user_id = await get_user_context(current_user) # AgentService mit Benutzerkontext initialisieren - agent_service = get_agent_service(mandate_id, user_id) + agent_service = get_agentservice_interface(mandate_id, user_id) logs = agent_service.get_workflow_logs(workflow_id) if logs is None: @@ -136,7 +136,7 @@ async def get_workflow_results( mandate_id, user_id = await get_user_context(current_user) # AgentService mit Benutzerkontext initialisieren - agent_service = get_agent_service(mandate_id, user_id) + agent_service = get_agentservice_interface(mandate_id, user_id) results = agent_service.get_workflow_results(workflow_id) if results is None: diff --git a/gwserver/specification.txt b/gwserver/specification.txt index 16d0006a..c88d142f 100644 --- a/gwserver/specification.txt +++ b/gwserver/specification.txt @@ -119,4 +119,30 @@ Enhanced Type Safety: More explicit typing and model definitions. Improved Maintainability: Modular code structure makes it easier to maintain and extend. Centralized Authentication: All auth logic in one place for better security. -This structure will allow for easier future expansion and maintenance of the system. \ No newline at end of file +This structure will allow for easier future expansion and maintenance of the system. + + + + + +.......................... Tasks + + + + +Kannst du mir bitte folgende anpassungen am code machen: + +Beim gateway_model.py "User" das Attribut "role" ergänzen mit allen Details. Dies ist die Berechtigungsrolle, sie kann eine dieser Optionen sein: [root,admin,user]. Wenn kein User vorhanden ist, wird ein User mit der Rolle "root" erzeugt. dieser wird dann auch gleich als aktiver user genutzt. + +Im Datenmodell den Datentyp "Lookup" oder ähnlich ergänzen, für Felder mit Auswahl, so wie die Auswahlobjekte für Berechtigungsrolle als Beispiel + +Im "lucydom_interface.py" einen default workspace erstellen, falls keiner vorhanden ist. dieser soll mit den credentials des angemendeten users stattfinden. + +unique id's bei datenobjekten in einer tabelle sicherstellen. über alle datensätze ist eine id eindeutig über mandanten hinweg. das management der id's soll im Connector "connector_bd_json.py" erfolgen. In den "...interface.py" dies rausnehmen. wenn ein neues datenobjekt erstellt wird, so erhält es die nächste verfügbare id. hier soll sichergestellt werden, dass bei parallelen funktionsaufrufen von mehreren Users nicht eine id doppelt gesetzt wird. + +Bei der Ausgabe eines datensatzes soll die ID immer als schreibgeschützt mitgegeben werden. + +Im Frontend soll im generischen Formular "generic-entity.js" für ein neues Objekt die ID entweder hidden oder schreibgeschützt sein. die ID wird nicht benötigt, sondern wird erst mit dem speichern in der datenbank erstellt. d.h. nach dem speichern in der datenbank werden die daten der entsprechenden tabelle neu geladen. + +In den Einstellungen des Frontends soll die Sprache des aktiven benutzers gemäss den Listenoptionen in den "...model.py" angepasst werden können. die sprache gilt dann auch für die Attributnamen in einem Formularfeld im "generic-entity.js". eine sprachänderung zieht somit eine anpassung des Users über das API nach sich, indem die Sprache in der Datenbank angepasst wird. + diff --git a/gwserver/static/favicon.ico b/gwserver/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 diff --git a/readme.md b/readme.md index 722bb24d..5dcc0bc3 100644 --- a/readme.md +++ b/readme.md @@ -61,7 +61,10 @@ Configuration: Environment varibales: - Neue Variable PORT=8000 - +### DEV TOOLS +Kill all processes on port 8000 +netsh advfirewall firewall add rule name="Close_Port_8000" dir=in action=block protocol=TCP localport=8000 +netsh advfirewall firewall delete rule name="Close_Port_8000" ### Datenbank-Migration