diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..82f92755
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,162 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
+.pdm.toml
+.pdm-python
+.pdm-build/
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
diff --git a/backend/agent_service.py b/backend/agent_service.py
new file mode 100644
index 00000000..8cf6d4a2
--- /dev/null
+++ b/backend/agent_service.py
@@ -0,0 +1,255 @@
+import asyncio
+import uuid
+from typing import List, Dict, Any, Optional
+from datetime import datetime
+import logging
+import json
+import os
+
+logger = logging.getLogger(__name__)
+
+class AgentService:
+ """
+ Service für die Verwaltung und Ausführung von Multi-Agent-Workflows.
+ In einer Produktionsumgebung würde hier eine Integration mit echten KI-Diensten
+ und eine Orchestrierung der verschiedenen Agenten stattfinden.
+ """
+
+ def __init__(self):
+ self.workflows = {}
+ self.results_dir = "./results"
+ os.makedirs(self.results_dir, exist_ok=True)
+
+ async def execute_workflow(
+ self,
+ workflow_id: str,
+ prompt: str,
+ agents: List[Dict[str, Any]],
+ files: List[Dict[str, Any]]
+ ) -> str:
+ """
+ Führt einen Workflow mit den angegebenen Agenten und Dateien aus.
+ Diese Methode simuliert die Ausführung für Demo-Zwecke.
+
+ In einer echten Implementierung würde hier die tatsächliche Orchestrierung
+ der Agenten und die Verarbeitung der Dateien stattfinden.
+ """
+ logger.info(f"Starte Workflow {workflow_id} mit {len(agents)} Agenten und {len(files)} Dateien")
+
+ # Workflow initialisieren
+ self.workflows[workflow_id] = {
+ "id": workflow_id,
+ "status": "running",
+ "progress": 0.0,
+ "started_at": datetime.now().isoformat(),
+ "completed_at": None,
+ "agent_statuses": {},
+ "logs": [],
+ "results": []
+ }
+
+ # Log-Eintrag für den Start des Workflows
+ self._add_log(workflow_id, "Workflow gestartet", "info")
+ self._add_log(workflow_id, f"Verarbeite {len(files)} Dateien...", "info")
+
+ self.workflows[workflow_id]["progress"] = 0.1
+ await asyncio.sleep(1) # Simuliere Verarbeitungszeit
+
+ # Verarbeitung pro Agent simulieren
+ for i, agent in enumerate(agents):
+ agent_id = agent["id"]
+ agent_name = agent["name"]
+ agent_type = agent["type"]
+
+ self.workflows[workflow_id]["agent_statuses"][agent_id] = "running"
+ self._add_log(
+ workflow_id,
+ f"Agent '{agent_name}' beginnt mit der Verarbeitung...",
+ "start",
+ agent_id,
+ agent_name
+ )
+
+ # Simuliere Verarbeitungszeit
+ self.workflows[workflow_id]["progress"] = 0.1 + (i + 1) * (0.8 / len(agents)) * 0.5
+ await asyncio.sleep(2)
+
+ # Agent-Ergebnis simulieren
+ result = self._generate_simulated_result(workflow_id, agent, i, prompt, files)
+ self.workflows[workflow_id]["results"].append(result)
+
+ self._add_log(
+ workflow_id,
+ f"Agent '{agent_name}' hat die Verarbeitung abgeschlossen",
+ "complete",
+ agent_id,
+ agent_name
+ )
+ self.workflows[workflow_id]["agent_statuses"][agent_id] = "completed"
+
+ self.workflows[workflow_id]["progress"] = 0.1 + (i + 1) * (0.8 / len(agents))
+ await asyncio.sleep(1)
+
+ # Workflow abschließen
+ self._add_log(workflow_id, "Alle Agenten haben ihre Aufgaben abgeschlossen", "info")
+ self._add_log(workflow_id, "Workflow erfolgreich beendet", "success")
+
+ self.workflows[workflow_id]["status"] = "completed"
+ self.workflows[workflow_id]["progress"] = 1.0
+ self.workflows[workflow_id]["completed_at"] = datetime.now().isoformat()
+
+ # Speichere Ergebnisse in Datei für spätere Verwendung
+ self._save_workflow_results(workflow_id)
+
+ return workflow_id
+
+ def _add_log(
+ self,
+ workflow_id: str,
+ message: str,
+ log_type: str,
+ agent_id: Optional[str] = None,
+ agent_name: Optional[str] = None
+ ) -> None:
+ """Fügt einen Protokolleintrag zum Workflow hinzu"""
+ log_entry = {
+ "id": f"log_{uuid.uuid4()}",
+ "message": message,
+ "type": log_type,
+ "timestamp": datetime.now().isoformat(),
+ "agent_id": agent_id,
+ "agent_name": agent_name
+ }
+
+ workflow = self.workflows.get(workflow_id)
+ if workflow:
+ workflow["logs"].append(log_entry)
+ logger.info(f"Workflow {workflow_id}: {message}")
+
+ def _generate_simulated_result(
+ self,
+ workflow_id: str,
+ agent: Dict[str, Any],
+ index: int,
+ prompt: str,
+ files: List[Dict[str, Any]]
+ ) -> Dict[str, Any]:
+ """Generiert ein simuliertes Ergebnis basierend auf dem Agententyp"""
+ agent_type = agent["type"]
+ agent_id = agent["id"]
+ agent_name = agent["name"]
+
+ # Dateien als Teil des Kontexts einbinden
+ file_names = [f["name"] for f in files]
+ file_context = f"Basierend auf der Analyse von: {', '.join(file_names)}"
+
+ result = {
+ "id": f"result_{workflow_id}_{index}",
+ "agent_id": agent_id,
+ "agent_name": agent_name,
+ "timestamp": datetime.now().isoformat(),
+ "metadata": {
+ "files_processed": file_names,
+ "prompt": prompt
+ }
+ }
+
+ if agent_type == "analyzer":
+ result.update({
+ "title": "Datenanalyse-Ergebnis",
+ "type": "text",
+ "content": f"{file_context}\n\nDie Analyse der bereitgestellten Daten zeigt folgende Schlüsselerkenntnisse:\n\n1. Die Umsätze stiegen im Q1 2025 um 12% im Vergleich zum Vorjahr\n2. Produktkategorie A zeigt die stärkste Leistung mit 23% Wachstum\n3. In den Regionen Nord und West wurde das größte Wachstum beobachtet\n4. Die Betriebskosten sind um 5% gesunken, was zu einer verbesserten Marge führt"
+ })
+ elif agent_type == "visualizer":
+ result.update({
+ "title": "Umsatzentwicklung nach Quartal",
+ "type": "chart",
+ "content": "Diagramm der Umsatzentwicklung (simuliert)",
+ "metadata": {
+ **result["metadata"],
+ "chart_type": "line",
+ "chart_data": {
+ "labels": ["Q1 2024", "Q2 2024", "Q3 2024", "Q4 2024", "Q1 2025"],
+ "datasets": [
+ {
+ "label": "Umsatz (Mio. €)",
+ "data": [2.1, 2.4, 2.8, 3.2, 3.6]
+ }
+ ]
+ }
+ }
+ })
+ elif agent_type == "writer":
+ result.update({
+ "title": "Zusammenfassung und Empfehlungen",
+ "type": "text",
+ "content": f"{file_context}\n\n# Zusammenfassung Q1 2025\n\nDie Unternehmensdaten zeigen ein starkes erstes Quartal mit signifikanten Verbesserungen in allen Geschäftsbereichen. Der Gesamtumsatz stieg um 12%, während die Betriebskosten um 5% sanken, was zu einer verbesserten Gewinnmarge führte.\n\n## Empfehlungen\n\n1. **Investitionen in Produktkategorie A ausbauen** - Diese Kategorie zeigt das stärkste Wachstum und sollte weiter gefördert werden\n2. **Marketingaktivitäten in Süd- und Ostregionen verstärken** - Diese Regionen zeigen Potenzial für Wachstum\n3. **Kostenoptimierungen beibehalten** - Die Maßnahmen zur Kostensenkung haben sich als effektiv erwiesen\n\n## Prognose für Q2 2025\n\nBei gleichbleibenden Marktbedingungen erwarten wir ein weiteres Wachstum von 8-10% im Q2 2025."
+ })
+ elif agent_type == "scraper":
+ result.update({
+ "title": "Web-Scraping Ergebnisse",
+ "type": "text",
+ "content": f"# Marktdaten aus Web-Quellen\n\nBasierend auf der Analyse von 15 führenden Branchenwebsites wurden folgende Trends identifiziert:\n\n1. Der Gesamtmarkt wächst mit einer Rate von 8,5% jährlich\n2. Hauptwettbewerber A und B haben kürzlich neue Produktlinien gestartet\n3. Die durchschnittlichen Marktpreise sind in den letzten 6 Monaten um 3,2% gestiegen"
+ })
+ else:
+ result.update({
+ "title": "Verarbeitungsergebnis",
+ "type": "text",
+ "content": f"Allgemeines Ergebnis der Verarbeitung durch {agent_name}"
+ })
+
+ return result
+
+ def _save_workflow_results(self, workflow_id: str) -> None:
+ """Speichert die Workflow-Ergebnisse in einer Datei"""
+ workflow = self.workflows.get(workflow_id)
+ if workflow:
+ try:
+ file_path = os.path.join(self.results_dir, f"workflow_{workflow_id}.json")
+ with open(file_path, 'w', encoding='utf-8') as f:
+ json.dump(workflow, f, indent=2, ensure_ascii=False)
+ logger.info(f"Workflow-Ergebnisse gespeichert: {file_path}")
+ except Exception as e:
+ logger.error(f"Fehler beim Speichern der Workflow-Ergebnisse: {e}")
+
+ def get_workflow_status(self, workflow_id: str) -> Optional[Dict[str, Any]]:
+ """Gibt den Status eines Workflows zurück"""
+ workflow = self.workflows.get(workflow_id)
+ if not workflow:
+ return None
+
+ return {
+ "id": workflow["id"],
+ "status": workflow["status"],
+ "progress": workflow["progress"],
+ "started_at": workflow["started_at"],
+ "completed_at": workflow["completed_at"],
+ "agent_statuses": workflow["agent_statuses"]
+ }
+
+ def get_workflow_logs(self, workflow_id: str) -> Optional[List[Dict[str, Any]]]:
+ """Gibt die Protokolle eines Workflows zurück"""
+ workflow = self.workflows.get(workflow_id)
+ if not workflow:
+ return None
+
+ return workflow["logs"]
+
+ def get_workflow_results(self, workflow_id: str) -> Optional[List[Dict[str, Any]]]:
+ """Gibt die Ergebnisse eines Workflows zurück"""
+ workflow = self.workflows.get(workflow_id)
+ if not workflow:
+ return None
+
+ return workflow["results"]
+
+
+# Singleton-Instanz des AgentService
+_agent_service_instance = None
+
+def get_agent_service():
+ """Gibt eine Singleton-Instanz des AgentService zurück"""
+ global _agent_service_instance
+ if _agent_service_instance is None:
+ _agent_service_instance = AgentService()
+ return _agent_service_instance
\ No newline at end of file
diff --git a/backend/app.py b/backend/app.py
new file mode 100644
index 00000000..458dce16
--- /dev/null
+++ b/backend/app.py
@@ -0,0 +1,305 @@
+from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, Body, Query
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import JSONResponse
+from fastapi.staticfiles import StaticFiles
+from typing import List, Dict, Any, Optional
+import uuid
+import os
+import json
+import asyncio
+from datetime import datetime
+import logging
+
+from models import (
+ Workspace,
+ Agent,
+ DataObject,
+ Prompt,
+ WorkflowRequest,
+ WorkflowResponse,
+ LogEntry,
+ Result
+)
+from database import get_db, Database
+from agent_service import AgentService, get_agent_service
+
+# Konfiguration des Loggers
+logging.basicConfig(
+ level=logging.INFO,
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ handlers=[logging.StreamHandler()]
+)
+logger = logging.getLogger(__name__)
+
+app = FastAPI(title="Data Platform API", description="Backend-API für die Multi-Agent Datenplattform")
+
+# CORS-Konfiguration für Frontend-Anfragen
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["*"], # In Produktion einschränken
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
+
+# Statische Dateien (für Frontend)
+app.mount("/static", StaticFiles(directory="static"), name="static")
+
+# Verzeichnis für hochgeladene Dateien erstellen
+UPLOAD_DIR = os.path.join(os.getcwd(), "uploads")
+os.makedirs(UPLOAD_DIR, exist_ok=True)
+
+
+@app.get("/", tags=["General"])
+async def root():
+ """API-Statusendpunkt"""
+ return {"status": "online", "message": "Data Platform API ist aktiv"}
+
+
+# Workspace-Endpunkte
+@app.get("/workspaces", tags=["Workspaces"], response_model=List[Workspace])
+async def get_workspaces(db: Database = Depends(get_db)):
+ """Alle verfügbaren Workspaces abrufen"""
+ return db.get_all_workspaces()
+
+
+@app.get("/workspaces/{workspace_id}", tags=["Workspaces"], response_model=Workspace)
+async def get_workspace(workspace_id: str, db: Database = Depends(get_db)):
+ """Einen bestimmten Workspace mit allen Details abrufen"""
+ workspace = db.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("/workspaces", tags=["Workspaces"], response_model=Workspace)
+async def create_workspace(workspace: Dict[str, Any] = Body(...), db: Database = Depends(get_db)):
+ """Einen neuen Workspace erstellen"""
+ workspace_id = str(uuid.uuid4())
+ workspace_data = {
+ "id": workspace_id,
+ "name": workspace.get("name", "Neuer Workspace"),
+ "created_at": datetime.now().isoformat(),
+ "prompts": [],
+ "agents": [],
+ "dataObjectReferences": []
+ }
+
+ new_workspace = db.create_workspace(workspace_data)
+ return new_workspace
+
+
+# Agent-Endpunkte
+@app.get("/agents", tags=["Agents"], response_model=List[Agent])
+async def get_agents(workspace_id: Optional[str] = Query(None), db: Database = Depends(get_db)):
+ """Alle Agenten oder Agenten eines bestimmten Workspaces abrufen"""
+ if workspace_id:
+ return db.get_agents_by_workspace(workspace_id)
+ return db.get_all_agents()
+
+
+@app.post("/agents", tags=["Agents"], response_model=Agent)
+async def create_agent(
+ agent: Dict[str, Any] = Body(...),
+ db: Database = Depends(get_db)
+):
+ """Einen neuen Agenten erstellen"""
+ agent_id = str(uuid.uuid4())
+ workspace_id = agent.get("workspace_id")
+
+ if not workspace_id:
+ raise HTTPException(status_code=400, detail="workspace_id ist erforderlich")
+
+ # Workspace existiert?
+ workspace = db.get_workspace(workspace_id)
+ if not workspace:
+ raise HTTPException(status_code=404, detail=f"Workspace mit ID {workspace_id} nicht gefunden")
+
+ agent_data = {
+ "id": agent_id,
+ "name": agent.get("name", "Neuer Agent"),
+ "type": agent.get("type", "generic"),
+ "capabilities": agent.get("capabilities", []),
+ "description": agent.get("description", "")
+ }
+
+ new_agent = db.create_agent(agent_data, workspace_id)
+ return new_agent
+
+
+# Datei-Endpunkte
+@app.get("/files", tags=["Files"], response_model=List[DataObject])
+async def get_files(db: Database = Depends(get_db)):
+ """Alle verfügbaren Dateien abrufen"""
+ return db.get_all_files()
+
+
+@app.post("/files/upload", tags=["Files"])
+async def upload_file(
+ file: UploadFile = File(...),
+ db: Database = Depends(get_db)
+):
+ """Eine Datei hochladen"""
+ try:
+ file_id = str(uuid.uuid4())
+ file_ext = os.path.splitext(file.filename)[1]
+ file_path = os.path.join(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
+ file_data = {
+ "id": file_id,
+ "name": file.filename,
+ "type": file_type,
+ "path": file_path,
+ "content_type": file.content_type,
+ "size": os.path.getsize(file_path),
+ "upload_date": datetime.now().isoformat()
+ }
+
+ new_file = db.create_file(file_data)
+
+ 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["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)}")
+
+
+# Prompt-Endpunkte
+@app.get("/prompts", tags=["Prompts"], response_model=List[Prompt])
+async def get_prompts(workspace_id: Optional[str] = Query(None), db: Database = Depends(get_db)):
+ """Alle Prompts oder Prompts eines bestimmten Workspaces abrufen"""
+ if workspace_id:
+ return db.get_prompts_by_workspace(workspace_id)
+ return db.get_all_prompts()
+
+
+@app.post("/prompts", tags=["Prompts"], response_model=Prompt)
+async def create_prompt(
+ prompt: Dict[str, Any] = Body(...),
+ db: Database = Depends(get_db)
+):
+ """Einen neuen Prompt erstellen"""
+ prompt_id = str(uuid.uuid4())
+ workspace_id = prompt.get("workspace_id")
+
+ if not workspace_id:
+ raise HTTPException(status_code=400, detail="workspace_id ist erforderlich")
+
+ # Workspace existiert?
+ workspace = db.get_workspace(workspace_id)
+ if not workspace:
+ raise HTTPException(status_code=404, detail=f"Workspace mit ID {workspace_id} nicht gefunden")
+
+ prompt_data = {
+ "id": prompt_id,
+ "content": prompt.get("content", ""),
+ "created_at": datetime.now().isoformat()
+ }
+
+ new_prompt = db.create_prompt(prompt_data, workspace_id)
+ return new_prompt
+
+
+# Workflow-Endpunkte
+@app.post("/workflow/run", tags=["Workflow"], response_model=WorkflowResponse)
+async def run_workflow(
+ workflow_request: WorkflowRequest,
+ db: Database = Depends(get_db),
+ agent_service: AgentService = Depends(get_agent_service)
+):
+ """Führt einen Workflow mit den ausgewählten Agenten und Dateien aus"""
+ workspace = db.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 = db.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 = db.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 WorkflowResponse(
+ workflow_id=workflow_id,
+ status="running",
+ message="Workflow wurde gestartet"
+ )
+
+
+@app.get("/workflow/{workflow_id}/status", tags=["Workflow"])
+async def get_workflow_status(
+ workflow_id: str,
+ agent_service: AgentService = Depends(get_agent_service)
+):
+ """Status eines laufenden Workflows abrufen"""
+ 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("/workflow/{workflow_id}/logs", tags=["Workflow"], response_model=List[LogEntry])
+async def get_workflow_logs(
+ workflow_id: str,
+ agent_service: AgentService = Depends(get_agent_service)
+):
+ """Protokolle eines Workflows abrufen"""
+ 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("/workflow/{workflow_id}/results", tags=["Workflow"], response_model=List[Result])
+async def get_workflow_results(
+ workflow_id: str,
+ agent_service: AgentService = Depends(get_agent_service)
+):
+ """Ergebnisse eines Workflows abrufen"""
+ 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
+
+
+if __name__ == "__main__":
+ import uvicorn
+ uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
\ No newline at end of file
diff --git a/backend/backend/__pycache__/__init__.cpython-311.pyc b/backend/backend/__pycache__/__init__.cpython-311.pyc
deleted file mode 100644
index 55864e2e..00000000
Binary files a/backend/backend/__pycache__/__init__.cpython-311.pyc and /dev/null differ
diff --git a/backend/backend/__pycache__/settings.cpython-311.pyc b/backend/backend/__pycache__/settings.cpython-311.pyc
deleted file mode 100644
index cdcbd4ef..00000000
Binary files a/backend/backend/__pycache__/settings.cpython-311.pyc and /dev/null differ
diff --git a/backend/backend/__pycache__/urls.cpython-311.pyc b/backend/backend/__pycache__/urls.cpython-311.pyc
deleted file mode 100644
index 7fda6d5f..00000000
Binary files a/backend/backend/__pycache__/urls.cpython-311.pyc and /dev/null differ
diff --git a/backend/backend/__pycache__/views.cpython-311.pyc b/backend/backend/__pycache__/views.cpython-311.pyc
deleted file mode 100644
index e4a839ef..00000000
Binary files a/backend/backend/__pycache__/views.cpython-311.pyc and /dev/null differ
diff --git a/backend/backend/__pycache__/wsgi.cpython-311.pyc b/backend/backend/__pycache__/wsgi.cpython-311.pyc
deleted file mode 100644
index dd3fa835..00000000
Binary files a/backend/backend/__pycache__/wsgi.cpython-311.pyc and /dev/null differ
diff --git a/backend/data/.gitignore b/backend/data/.gitignore
new file mode 100644
index 00000000..86d0cb27
--- /dev/null
+++ b/backend/data/.gitignore
@@ -0,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
\ No newline at end of file
diff --git a/backend/database.py b/backend/database.py
new file mode 100644
index 00000000..32c8281f
--- /dev/null
+++ b/backend/database.py
@@ -0,0 +1,299 @@
+import json
+import os
+from typing import List, Dict, Any, Optional
+from datetime import datetime
+import logging
+
+logger = logging.getLogger(__name__)
+
+class Database:
+ """
+ Eine einfache JSON-basierte Datenbank für die Datenspeicherung.
+ In einer Produktionsumgebung würde hier eine richtige Datenbank verwendet werden.
+ """
+
+ def __init__(self, data_dir="./data"):
+ self.data_dir = data_dir
+ os.makedirs(data_dir, exist_ok=True)
+
+ # Standardpfade für die Datendateien
+ self.workspaces_file = os.path.join(data_dir, "workspaces.json")
+ self.agents_file = os.path.join(data_dir, "agents.json")
+ self.files_file = os.path.join(data_dir, "files.json")
+ self.prompts_file = os.path.join(data_dir, "prompts.json")
+
+ # Initiale Daten erstellen, falls die Dateien nicht existieren
+ self._initialize_data()
+
+ def _initialize_data(self):
+ """Initialisiert die Datendateien, falls sie nicht existieren"""
+ # Workspaces
+ if not os.path.exists(self.workspaces_file):
+ self._save_data(self.workspaces_file, [
+ {
+ "id": "workspace_001",
+ "name": "Datenanalyse-Projekt",
+ "created_at": datetime.now().isoformat(),
+ "prompts": [],
+ "agents": [],
+ "dataObjectReferences": []
+ },
+ {
+ "id": "workspace_002",
+ "name": "Marktforschung",
+ "created_at": datetime.now().isoformat(),
+ "prompts": [],
+ "agents": [],
+ "dataObjectReferences": []
+ }
+ ])
+
+ # Agenten
+ if not os.path.exists(self.agents_file):
+ self._save_data(self.agents_file, [
+ {
+ "id": "agent_001",
+ "name": "Datenanalyse-Agent",
+ "type": "analyzer",
+ "workspace_id": "workspace_001",
+ "capabilities": ["Datenanalyse", "Statistik", "Trendanalyse"],
+ "description": "Spezialisiert auf die Analyse von strukturierten Daten"
+ },
+ {
+ "id": "agent_002",
+ "name": "Visualisierungs-Agent",
+ "type": "visualizer",
+ "workspace_id": "workspace_001",
+ "capabilities": ["Datenvisualisierung", "Diagrammerstellung"],
+ "description": "Erstellt visuelle Darstellungen aus Daten"
+ },
+ {
+ "id": "agent_003",
+ "name": "Text-Generator",
+ "type": "writer",
+ "workspace_id": "workspace_001",
+ "capabilities": ["Texterstellung", "Zusammenfassung"],
+ "description": "Verfasst verständliche Berichte und Zusammenfassungen"
+ },
+ {
+ "id": "agent_004",
+ "name": "Web-Scraper",
+ "type": "scraper",
+ "workspace_id": "workspace_002",
+ "capabilities": ["Datensammlung", "Webanalyse"],
+ "description": "Sammelt Daten aus verschiedenen Webquellen"
+ },
+ {
+ "id": "agent_005",
+ "name": "Marktanalyse-Agent",
+ "type": "analyzer",
+ "workspace_id": "workspace_002",
+ "capabilities": ["Wettbewerbsanalyse", "Markttrends"],
+ "description": "Spezialisiert auf Wettbewerbsanalyse und Markttrends"
+ }
+ ])
+
+ # Dateien
+ if not os.path.exists(self.files_file):
+ self._save_data(self.files_file, [
+ {
+ "id": "file_001",
+ "name": "Quartalsbericht Q1 2025.pdf",
+ "type": "document",
+ "content_type": "application/pdf",
+ "size": 2500000, # ca. 2.4 MB
+ "upload_date": datetime.now().isoformat(),
+ "path": "uploads/dummy_file1.pdf"
+ },
+ {
+ "id": "file_002",
+ "name": "Marktanalyse-Diagramm.png",
+ "type": "image",
+ "content_type": "image/png",
+ "size": 1150000, # ca. 1.1 MB
+ "upload_date": datetime.now().isoformat(),
+ "path": "uploads/dummy_file2.png"
+ },
+ {
+ "id": "file_003",
+ "name": "Finanzdaten_2024-2025.xlsx",
+ "type": "document",
+ "content_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ "size": 3880000, # ca. 3.7 MB
+ "upload_date": datetime.now().isoformat(),
+ "path": "uploads/dummy_file3.xlsx"
+ }
+ ])
+
+ # Prompts
+ if not os.path.exists(self.prompts_file):
+ self._save_data(self.prompts_file, [
+ {
+ "id": "prompt_001",
+ "content": "Analysiere die Quartalsdaten und erstelle eine Zusammenfassung mit wichtigsten Trends",
+ "workspace_id": "workspace_001",
+ "created_at": datetime.now().isoformat()
+ },
+ {
+ "id": "prompt_002",
+ "content": "Erstelle eine Prognose für das nächste Quartal basierend auf historischen Daten",
+ "workspace_id": "workspace_001",
+ "created_at": datetime.now().isoformat()
+ }
+ ])
+
+ def _load_data(self, file_path):
+ """Lädt Daten aus einer JSON-Datei"""
+ try:
+ if os.path.exists(file_path):
+ with open(file_path, 'r', encoding='utf-8') as f:
+ return json.load(f)
+ return []
+ except Exception as e:
+ logger.error(f"Fehler beim Laden der Daten aus {file_path}: {e}")
+ return []
+
+ def _save_data(self, file_path, data):
+ """Speichert Daten in einer JSON-Datei"""
+ try:
+ with open(file_path, 'w', encoding='utf-8') as f:
+ json.dump(data, f, indent=2, ensure_ascii=False)
+ except Exception as e:
+ logger.error(f"Fehler beim Speichern der Daten in {file_path}: {e}")
+
+ # Workspace-Methoden
+ def get_all_workspaces(self) -> List[Dict[str, Any]]:
+ """Gibt alle Workspaces zurück"""
+ workspaces = self._load_data(self.workspaces_file)
+
+ # Füge Agenten und Prompts hinzu
+ for workspace in workspaces:
+ workspace_id = workspace["id"]
+ workspace["agents"] = self.get_agents_by_workspace(workspace_id)
+ workspace["prompts"] = self.get_prompts_by_workspace(workspace_id)
+
+ return workspaces
+
+ def get_workspace(self, workspace_id: str) -> Optional[Dict[str, Any]]:
+ """Gibt einen Workspace anhand seiner ID zurück"""
+ workspaces = self._load_data(self.workspaces_file)
+ for workspace in workspaces:
+ if workspace["id"] == workspace_id:
+ # Füge Agenten und Prompts hinzu
+ workspace["agents"] = self.get_agents_by_workspace(workspace_id)
+ workspace["prompts"] = self.get_prompts_by_workspace(workspace_id)
+ return workspace
+ return None
+
+ def create_workspace(self, workspace_data: Dict[str, Any]) -> Dict[str, Any]:
+ """Erstellt einen neuen Workspace"""
+ workspaces = self._load_data(self.workspaces_file)
+ workspaces.append(workspace_data)
+ self._save_data(self.workspaces_file, workspaces)
+ return workspace_data
+
+ # Agent-Methoden
+ def get_all_agents(self) -> List[Dict[str, Any]]:
+ """Gibt alle Agenten zurück"""
+ return self._load_data(self.agents_file)
+
+ def get_agents_by_workspace(self, workspace_id: str) -> List[Dict[str, Any]]:
+ """Gibt alle Agenten eines Workspaces zurück"""
+ agents = self._load_data(self.agents_file)
+ return [agent for agent in agents if agent.get("workspace_id") == workspace_id]
+
+ def get_agent(self, agent_id: str) -> Optional[Dict[str, Any]]:
+ """Gibt einen Agenten anhand seiner ID zurück"""
+ agents = self._load_data(self.agents_file)
+ for agent in agents:
+ if agent["id"] == agent_id:
+ return agent
+ return None
+
+ def create_agent(self, agent_data: Dict[str, Any], workspace_id: str) -> Dict[str, Any]:
+ """Erstellt einen neuen Agenten"""
+ agents = self._load_data(self.agents_file)
+ agent_data["workspace_id"] = workspace_id
+ agents.append(agent_data)
+ self._save_data(self.agents_file, agents)
+
+ # Aktualisiere die Workspace-Referenz
+ workspaces = self._load_data(self.workspaces_file)
+ for workspace in workspaces:
+ if workspace["id"] == workspace_id:
+ if "agents" not in workspace:
+ workspace["agents"] = []
+ workspace["agents"].append(agent_data["id"])
+ break
+ self._save_data(self.workspaces_file, workspaces)
+
+ return agent_data
+
+ # Datei-Methoden
+ def get_all_files(self) -> List[Dict[str, Any]]:
+ """Gibt alle Dateien zurück"""
+ files = self._load_data(self.files_file)
+ return files
+
+ def get_file(self, file_id: str) -> Optional[Dict[str, Any]]:
+ """Gibt eine Datei anhand ihrer ID zurück"""
+ files = self._load_data(self.files_file)
+ for file in files:
+ if file["id"] == file_id:
+ return file
+ return None
+
+ def create_file(self, file_data: Dict[str, Any]) -> Dict[str, Any]:
+ """Erstellt einen neuen Dateieintrag"""
+ files = self._load_data(self.files_file)
+ files.append(file_data)
+ self._save_data(self.files_file, files)
+ return file_data
+
+ # Prompt-Methoden
+ def get_all_prompts(self) -> List[Dict[str, Any]]:
+ """Gibt alle Prompts zurück"""
+ return self._load_data(self.prompts_file)
+
+ def get_prompts_by_workspace(self, workspace_id: str) -> List[Dict[str, Any]]:
+ """Gibt alle Prompts eines Workspaces zurück"""
+ prompts = self._load_data(self.prompts_file)
+ return [prompt for prompt in prompts if prompt.get("workspace_id") == workspace_id]
+
+ def get_prompt(self, prompt_id: str) -> Optional[Dict[str, Any]]:
+ """Gibt einen Prompt anhand seiner ID zurück"""
+ prompts = self._load_data(self.prompts_file)
+ for prompt in prompts:
+ if prompt["id"] == prompt_id:
+ return prompt
+ return None
+
+ def create_prompt(self, prompt_data: Dict[str, Any], workspace_id: str) -> Dict[str, Any]:
+ """Erstellt einen neuen Prompt"""
+ prompts = self._load_data(self.prompts_file)
+ prompt_data["workspace_id"] = workspace_id
+ prompts.append(prompt_data)
+ self._save_data(self.prompts_file, prompts)
+
+ # Aktualisiere die Workspace-Referenz
+ workspaces = self._load_data(self.workspaces_file)
+ for workspace in workspaces:
+ if workspace["id"] == workspace_id:
+ if "prompts" not in workspace:
+ workspace["prompts"] = []
+ workspace["prompts"].append(prompt_data["id"])
+ break
+ self._save_data(self.workspaces_file, workspaces)
+
+ return prompt_data
+
+
+# Singleton-Instanz der Datenbank
+_db_instance = None
+
+def get_db():
+ """Gibt eine Singleton-Instanz der Datenbank zurück"""
+ global _db_instance
+ if _db_instance is None:
+ _db_instance = Database()
+ return _db_instance
\ No newline at end of file
diff --git a/backend/db.sqlite3 b/backend/db.sqlite3
deleted file mode 100644
index 02356a0d..00000000
Binary files a/backend/db.sqlite3 and /dev/null differ
diff --git a/backend/models.py b/backend/models.py
new file mode 100644
index 00000000..b3ba9909
--- /dev/null
+++ b/backend/models.py
@@ -0,0 +1,80 @@
+from pydantic import BaseModel, Field
+from typing import List, Dict, Any, Optional
+from datetime import datetime
+
+class Agent(BaseModel):
+ """Datenmodell für einen Agenten"""
+ id: str
+ name: str
+ type: str
+ capabilities: List[str] = []
+ description: Optional[str] = None
+
+class DataObject(BaseModel):
+ """Datenmodell für ein Datenobjekt"""
+ id: str
+ name: str
+ type: str # 'document', 'image', etc.
+ size: Optional[str] = None
+ upload_date: Optional[str] = None
+ content_type: Optional[str] = None
+ path: Optional[str] = None
+
+class Prompt(BaseModel):
+ """Datenmodell für einen Prompt"""
+ id: str
+ content: str
+ created_at: str
+
+class Workspace(BaseModel):
+ """Datenmodell für einen Workspace"""
+ id: str
+ name: str
+ created_at: str
+ prompts: List[Prompt] = []
+ agents: List[Agent] = []
+ dataObjectReferences: List[str] = []
+
+class WorkflowRequest(BaseModel):
+ """Anforderung zur Ausführung eines Workflows"""
+ workspace_id: str
+ prompt: str
+ agents: List[str] # Liste von Agent-IDs
+ files: List[str] # Liste von Datei-IDs
+ workflow_name: Optional[str] = None
+
+class WorkflowResponse(BaseModel):
+ """Antwort nach dem Start eines Workflows"""
+ workflow_id: str
+ status: str
+ message: str
+
+class LogEntry(BaseModel):
+ """Protokolleintrag während der Workflow-Ausführung"""
+ id: str
+ message: str
+ type: str # 'info', 'error', 'start', 'complete', 'success'
+ timestamp: str
+ agent_id: Optional[str] = None
+ agent_name: Optional[str] = None
+
+class Result(BaseModel):
+ """Ergebnis eines Agenten während der Workflow-Ausführung"""
+ id: str
+ title: str
+ agent_id: str
+ agent_name: str
+ type: str # 'text', 'chart', 'image', etc.
+ content: str
+ timestamp: str
+ metadata: Optional[Dict[str, Any]] = None
+
+class WorkflowStatus(BaseModel):
+ """Status eines Workflows"""
+ id: str
+ name: Optional[str] = None
+ status: str # 'running', 'completed', 'failed'
+ progress: float # 0.0 - 1.0
+ started_at: str
+ completed_at: Optional[str] = None
+ agent_statuses: Optional[Dict[str, str]] = None
\ No newline at end of file
diff --git a/backend/results/.gitignore b/backend/results/.gitignore
new file mode 100644
index 00000000..86d0cb27
--- /dev/null
+++ b/backend/results/.gitignore
@@ -0,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
\ No newline at end of file
diff --git a/backend/static/.gitignore b/backend/static/.gitignore
new file mode 100644
index 00000000..86d0cb27
--- /dev/null
+++ b/backend/static/.gitignore
@@ -0,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
\ No newline at end of file
diff --git a/backend/uploads/.gitignore b/backend/uploads/.gitignore
new file mode 100644
index 00000000..86d0cb27
--- /dev/null
+++ b/backend/uploads/.gitignore
@@ -0,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
\ No newline at end of file
diff --git a/frontend/frontend/.env b/frontend/frontend/.env
deleted file mode 100644
index 294190f6..00000000
--- a/frontend/frontend/.env
+++ /dev/null
@@ -1 +0,0 @@
-REACT_APP_API_URL=https://volucy-gateway-e3d2bzbxdeaaayhz.westeurope-01.azurewebsites.net
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 00000000..e3458bd3
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,204 @@
+
+
+
+
+
+ Data Platform - Multi-Agent Service
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Meine Prompts
+
+
Gespeicherte Prompts
+
+
+
+
+
+
+
+
+
+
Meine Agents
+
+
Verfügbare Agents
+
+
+
+
+
+
+
+
+
+
+
Workflow-Konfiguration
+
+
+
+
1. Dateien auswählen
+
+
+
+
+
+
Ausgewählte Dateien
+
+
+
+ Keine Dateien ausgewählt
+
+
+
+
+
+
+
+
+
+
2. Prompt eingeben
+
+
+
+
+
+
3. Agenten auswählen
+
+
+
+
+
+
+
+
+
+
+
+
+
Ausführung & Ergebnisse
+
+
+
+
Ausführungsprotokoll
+
+
Workflow noch nicht gestartet. Protokoll wird hier angezeigt.
+
+
+
+
+
+
Ergebnisse
+
+
+
Keine Ergebnisse verfügbar
+
Führen Sie einen Workflow aus, um Ergebnisse zu sehen
+
+
+
+
+
+
+
+
+
+
+
+
Meine Daten
+
+
Verfügbare Dateien
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/script.js b/frontend/script.js
new file mode 100644
index 00000000..77ceeabd
--- /dev/null
+++ b/frontend/script.js
@@ -0,0 +1,654 @@
+document.addEventListener('DOMContentLoaded', function() {
+ // Backend API Basis-URL
+ const API_BASE_URL = 'http://localhost:8000';
+
+ // Status-Management
+ let state = {
+ workspaces: [],
+ files: [],
+ currentWorkspace: null,
+ selectedFiles: [],
+ selectedAgents: [],
+ prompt: "",
+ logs: [],
+ results: [],
+ isRunning: false,
+ currentWorkflowId: null
+ };
+ let activeView = 'workflow'; // Standard-Ansicht beim Start
+
+ // DOM-Elemente für Menüpunkte
+ const workflowLink = document.querySelector('a[href="#workflow"]').parentElement;
+ const dataLink = document.querySelector('a[href="#data"]').parentElement;
+ const promptsLink = document.querySelector('a[href="#prompts"]').parentElement;
+ const agentsLink = document.querySelector('a[href="#agents"]').parentElement;
+
+ // DOM-Elemente für die Ansichten
+ const workflowView = document.querySelector('.workflow-container');
+ const dataView = document.getElementById('data-view');
+ const promptsView = document.getElementById('prompts-view');
+ const agentsView = document.getElementById('agents-view');
+
+ // DOM-Elemente
+ const workspaceList = document.getElementById('workspace-list');
+ const availableFiles = document.getElementById('available-files');
+ const selectedFiles = document.getElementById('selected-files');
+ const emptyFilesState = document.getElementById('empty-files-state');
+ const fileInput = document.getElementById('file-input');
+ const uploadBtn = document.getElementById('upload-btn');
+ const promptInput = document.getElementById('prompt-input');
+ const agentList = document.getElementById('agent-list');
+ const resetBtn = document.getElementById('reset-btn');
+ const startWorkflowBtn = document.getElementById('start-workflow-btn');
+ const executionLog = document.getElementById('execution-log');
+ const resultsContainer = document.getElementById('results-container');
+ const emptyResultsState = document.getElementById('empty-results-state');
+ const resultsList = document.getElementById('results-list');
+
+ // API-Funktionen
+ async function fetchWorkspaces() {
+ try {
+ const response = await fetch(`${API_BASE_URL}/workspaces`);
+ if (!response.ok) throw new Error('Fehler beim Abrufen der Workspaces');
+
+ const workspaces = await response.json();
+ state.workspaces = workspaces;
+
+ if (workspaces.length > 0) {
+ state.currentWorkspace = workspaces[0];
+ }
+
+ renderWorkspaces();
+ return workspaces;
+ } catch (error) {
+ console.error('Fehler beim Abrufen der Workspaces:', error);
+ return [];
+ }
+ }
+
+ async function fetchFiles() {
+ try {
+ const response = await fetch(`${API_BASE_URL}/files`);
+ if (!response.ok) throw new Error('Fehler beim Abrufen der Dateien');
+
+ const files = await response.json();
+ state.files = files;
+
+ renderFiles();
+ return files;
+ } catch (error) {
+ console.error('Fehler beim Abrufen der Dateien:', error);
+ return [];
+ }
+ }
+
+ async function uploadFile(file) {
+ try {
+ const formData = new FormData();
+ formData.append('file', file);
+
+ const response = await fetch(`${API_BASE_URL}/files/upload`, {
+ method: 'POST',
+ body: formData
+ });
+
+ if (!response.ok) throw new Error('Fehler beim Hochladen der Datei');
+
+ const newFile = await response.json();
+ return newFile;
+ } catch (error) {
+ console.error('Fehler beim Hochladen der Datei:', error);
+ return null;
+ }
+ }
+
+ async function runWorkflowAPI(workflowData) {
+ try {
+ const response = await fetch(`${API_BASE_URL}/workflow/run`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(workflowData)
+ });
+
+ if (!response.ok) throw new Error('Fehler beim Starten des Workflows');
+
+ const result = await response.json();
+ return result;
+ } catch (error) {
+ console.error('Fehler beim Starten des Workflows:', error);
+ return null;
+ }
+ }
+
+ async function fetchWorkflowStatus(workflowId) {
+ try {
+ const response = await fetch(`${API_BASE_URL}/workflow/${workflowId}/status`);
+ if (!response.ok) throw new Error('Fehler beim Abrufen des Workflow-Status');
+
+ const status = await response.json();
+ return status;
+ } catch (error) {
+ console.error('Fehler beim Abrufen des Workflow-Status:', error);
+ return null;
+ }
+ }
+
+ async function fetchWorkflowLogs(workflowId) {
+ try {
+ const response = await fetch(`${API_BASE_URL}/workflow/${workflowId}/logs`);
+ if (!response.ok) throw new Error('Fehler beim Abrufen der Workflow-Logs');
+
+ const logs = await response.json();
+ return logs;
+ } catch (error) {
+ console.error('Fehler beim Abrufen der Workflow-Logs:', error);
+ return [];
+ }
+ }
+
+ async function fetchWorkflowResults(workflowId) {
+ try {
+ const response = await fetch(`${API_BASE_URL}/workflow/${workflowId}/results`);
+ if (!response.ok) throw new Error('Fehler beim Abrufen der Workflow-Ergebnisse');
+
+ const results = await response.json();
+ return results;
+ } catch (error) {
+ console.error('Fehler beim Abrufen der Workflow-Ergebnisse:', error);
+ return [];
+ }
+ }
+
+ // Funktion zum Wechseln der Ansicht
+ function setActiveView(view) {
+ // Aktive Klasse von allen Menüpunkten entfernen
+ workflowLink.classList.remove('active');
+ dataLink.classList.remove('active');
+ promptsLink.classList.remove('active');
+ agentsLink.classList.remove('active');
+
+ // Alle Ansichten ausblenden
+ workflowView.style.display = 'none';
+ dataView.style.display = 'none';
+ promptsView.style.display = 'none';
+ agentsView.style.display = 'none';
+
+ // Aktive Ansicht und Menüpunkt setzen
+ activeView = view;
+
+ switch (view) {
+ case 'workflow':
+ workflowLink.classList.add('active');
+ workflowView.style.display = 'flex';
+ break;
+ case 'data':
+ dataLink.classList.add('active');
+ dataView.style.display = 'block';
+ // Aktualisiere Daten bei Ansichtswechsel
+ fetchFiles();
+ break;
+ case 'prompts':
+ promptsLink.classList.add('active');
+ promptsView.style.display = 'block';
+ // Prompts laden wenn wir die Ansicht wechseln
+ if (state.currentWorkspace) {
+ renderPrompts();
+ }
+ break;
+ case 'agents':
+ agentsLink.classList.add('active');
+ agentsView.style.display = 'block';
+ // Agenten laden wenn wir die Ansicht wechseln
+ if (state.currentWorkspace) {
+ renderAgentsList();
+ }
+ break;
+ }
+ }
+
+ // Workspaces rendern
+ function renderWorkspaces() {
+ workspaceList.innerHTML = '';
+ state.workspaces.forEach(workspace => {
+ const li = document.createElement('li');
+ li.className = `workspace-item ${workspace.id === state.currentWorkspace?.id ? 'active' : ''}`;
+ li.innerHTML = `
+
+ ${workspace.name}
+ `;
+ li.addEventListener('click', async () => {
+ state.currentWorkspace = workspace;
+ state.selectedFiles = [];
+ state.selectedAgents = [];
+ state.prompt = "";
+
+ renderWorkspaces();
+ renderFiles();
+ renderSelectedFiles();
+ renderAgents();
+ });
+ workspaceList.appendChild(li);
+ });
+ }
+
+ // Verfügbare Dateien rendern
+ function renderFiles() {
+ availableFiles.innerHTML = '';
+ state.files.forEach(file => {
+ const li = document.createElement('li');
+ li.className = 'file-item';
+ const isSelected = state.selectedFiles.some(f => f.id === file.id);
+ li.innerHTML = `
+
+
+
+
+ ${file.size || ''}
+ `;
+ const checkbox = li.querySelector(`#file-${file.id}`);
+ checkbox.addEventListener('change', () => {
+ if (checkbox.checked) {
+ state.selectedFiles.push(file);
+ } else {
+ state.selectedFiles = state.selectedFiles.filter(f => f.id !== file.id);
+ }
+ renderSelectedFiles();
+ });
+ availableFiles.appendChild(li);
+ });
+ }
+
+ // Ausgewählte Dateien rendern
+ function renderSelectedFiles() {
+ if (state.selectedFiles.length === 0) {
+ emptyFilesState.style.display = 'flex';
+ selectedFiles.innerHTML = '';
+ } else {
+ emptyFilesState.style.display = 'none';
+ selectedFiles.innerHTML = '';
+ state.selectedFiles.forEach(file => {
+ const li = document.createElement('li');
+ li.className = 'selected-file-item';
+ li.innerHTML = `
+
+
+ ${file.name}
+
+
+ `;
+ selectedFiles.appendChild(li);
+ });
+
+ // Event-Listener für Entfernen-Buttons
+ document.querySelectorAll('.remove-file-btn').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const fileId = btn.getAttribute('data-id');
+ state.selectedFiles = state.selectedFiles.filter(f => f.id !== fileId);
+ renderSelectedFiles();
+ renderFiles();
+ });
+ });
+ }
+ }
+
+ // Prompts rendern
+ function renderPrompts() {
+ const promptsContainer = document.getElementById('prompts-list');
+ if (!promptsContainer) return;
+
+ promptsContainer.innerHTML = '';
+
+ if (!state.currentWorkspace || !state.currentWorkspace.prompts || state.currentWorkspace.prompts.length === 0) {
+ promptsContainer.innerHTML = 'Keine Prompts im aktuellen Workspace verfügbar.
';
+ return;
+ }
+
+ state.currentWorkspace.prompts.forEach(prompt => {
+ const promptItem = document.createElement('div');
+ promptItem.className = 'prompt-item';
+ promptItem.innerHTML = `
+ ${prompt.content}
+ Erstellt am: ${new Date(prompt.created_at).toLocaleString()}
+
+
+
+ `;
+
+ // Event-Listener für "Verwenden"-Button
+ const useBtn = promptItem.querySelector('.use-prompt-btn');
+ useBtn.addEventListener('click', () => {
+ // Prompt zum aktuellen Workflow hinzufügen
+ promptInput.value = prompt.content;
+ state.prompt = prompt.content;
+ setActiveView('workflow');
+ });
+
+ promptsContainer.appendChild(promptItem);
+ });
+ }
+
+ // Agentenliste rendern
+ function renderAgentsList() {
+ const agentsContainer = document.getElementById('agents-list-container');
+ if (!agentsContainer) return;
+
+ agentsContainer.innerHTML = '';
+
+ if (!state.currentWorkspace || !state.currentWorkspace.agents || state.currentWorkspace.agents.length === 0) {
+ agentsContainer.innerHTML = 'Keine Agenten im aktuellen Workspace verfügbar.
';
+ return;
+ }
+
+ state.currentWorkspace.agents.forEach(agent => {
+ const agentItem = document.createElement('div');
+ agentItem.className = 'agent-list-item';
+ agentItem.innerHTML = `
+
+ ${agent.description || ''}
+
+ ${agent.capabilities ? agent.capabilities.map(cap => `${cap}`).join('') : ''}
+
+ `;
+ agentsContainer.appendChild(agentItem);
+ });
+ }
+
+ // Agenten für Workflow rendern
+ function renderAgents() {
+ agentList.innerHTML = '';
+ if (!state.currentWorkspace || !state.currentWorkspace.agents || state.currentWorkspace.agents.length === 0) {
+ agentList.innerHTML = 'Keine Agenten im ausgewählten Workspace verfügbar.
';
+ return;
+ }
+
+ state.currentWorkspace.agents.forEach(agent => {
+ const li = document.createElement('li');
+ li.className = 'agent-item';
+ const isSelected = state.selectedAgents.some(a => a.id === agent.id);
+ li.innerHTML = `
+
+ ${agent.description || ''}
+ `;
+ const checkbox = li.querySelector(`#agent-${agent.id}`);
+ checkbox.addEventListener('change', () => {
+ if (checkbox.checked) {
+ state.selectedAgents.push(agent);
+ } else {
+ state.selectedAgents = state.selectedAgents.filter(a => a.id !== agent.id);
+ }
+ });
+ agentList.appendChild(li);
+ });
+ }
+
+ // Ausführungsprotokoll aktualisieren und rendern
+ function updateLogs(logs) {
+ state.logs = logs;
+ renderLogs();
+ }
+
+ // Protokoll rendern
+ function renderLogs() {
+ if (state.logs.length === 0) {
+ executionLog.innerHTML = 'Workflow noch nicht gestartet. Protokoll wird hier angezeigt.
';
+ return;
+ }
+
+ executionLog.innerHTML = '';
+ state.logs.forEach(log => {
+ const logEntry = document.createElement('div');
+ logEntry.className = 'log-entry';
+ logEntry.innerHTML = `
+ [${new Date(log.timestamp).toLocaleTimeString()}]
+ ${log.message}
+ `;
+ executionLog.appendChild(logEntry);
+ });
+
+ // Zum Ende scrollen
+ executionLog.scrollTop = executionLog.scrollHeight;
+ }
+
+ // Ergebnisse aktualisieren und rendern
+ function updateResults(results) {
+ state.results = results;
+ renderResults();
+ }
+
+ // Ergebnisse rendern
+ function renderResults() {
+ if (state.results.length === 0) {
+ emptyResultsState.style.display = 'flex';
+ resultsList.style.display = 'none';
+ return;
+ }
+
+ emptyResultsState.style.display = 'none';
+ resultsList.style.display = 'block';
+ resultsList.innerHTML = '';
+
+ state.results.forEach(result => {
+ const resultItem = document.createElement('div');
+ resultItem.className = 'result-item';
+
+ if (result.type === 'text') {
+ resultItem.innerHTML = `
+
+ ${result.content}
+ `;
+ } else if (result.type === 'chart') {
+ resultItem.innerHTML = `
+
+
+
Umsatzentwicklung nach Quartal
+
${result.content}
+
+ `;
+ }
+
+ resultsList.appendChild(resultItem);
+ });
+ }
+
+ // Workflow-Ausführung
+ async function runWorkflow() {
+ if (state.selectedFiles.length === 0 || state.selectedAgents.length === 0 || !promptInput.value.trim()) {
+ alert("Bitte wählen Sie Dateien und Agenten aus und geben Sie einen Prompt ein.");
+ return;
+ }
+
+ state.isRunning = true;
+ state.prompt = promptInput.value;
+ state.logs = [];
+ state.results = [];
+
+ startWorkflowBtn.textContent = 'Wird ausgeführt...';
+ startWorkflowBtn.classList.add('running');
+ startWorkflowBtn.disabled = true;
+
+ renderLogs();
+ renderResults();
+
+ // API-Aufruf an das Backend
+ const workflowData = {
+ workspace_id: state.currentWorkspace.id,
+ files: state.selectedFiles.map(file => file.id),
+ agents: state.selectedAgents.map(agent => agent.id),
+ prompt: state.prompt,
+ workflow_name: "Workflow " + new Date().toLocaleString()
+ };
+
+ try {
+ // Workflow starten
+ const response = await runWorkflowAPI(workflowData);
+
+ if (response && response.workflow_id) {
+ state.currentWorkflowId = response.workflow_id;
+
+ // Polling für Status, Logs und Ergebnisse
+ pollWorkflowStatus();
+ } else {
+ throw new Error("Workflow konnte nicht gestartet werden");
+ }
+ } catch (error) {
+ console.error("Fehler beim Starten des Workflows:", error);
+ state.isRunning = false;
+ startWorkflowBtn.textContent = 'Workflow starten';
+ startWorkflowBtn.classList.remove('running');
+ startWorkflowBtn.disabled = false;
+
+ alert("Fehler beim Starten des Workflows: " + error.message);
+ }
+ }
+
+ // Polling für Workflow-Status, Logs und Ergebnisse
+ async function pollWorkflowStatus() {
+ if (!state.currentWorkflowId || !state.isRunning) return;
+
+ try {
+ // Status abrufen
+ const status = await fetchWorkflowStatus(state.currentWorkflowId);
+
+ // Logs abrufen und anzeigen
+ const logs = await fetchWorkflowLogs(state.currentWorkflowId);
+ updateLogs(logs);
+
+ // Wenn der Workflow abgeschlossen ist, Ergebnisse abrufen
+ if (status && (status.status === 'completed' || status.status === 'failed')) {
+ const results = await fetchWorkflowResults(state.currentWorkflowId);
+ updateResults(results);
+
+ state.isRunning = false;
+ startWorkflowBtn.textContent = 'Workflow starten';
+ startWorkflowBtn.classList.remove('running');
+ startWorkflowBtn.disabled = false;
+
+ return; // Polling beenden
+ }
+
+ // Weiteres Polling nach kurzer Verzögerung
+ setTimeout(pollWorkflowStatus, 2000);
+ } catch (error) {
+ console.error("Fehler beim Abrufen des Workflow-Status:", error);
+ state.isRunning = false;
+ startWorkflowBtn.textContent = 'Workflow starten';
+ startWorkflowBtn.classList.remove('running');
+ startWorkflowBtn.disabled = false;
+ }
+ }
+
+ // Zurücksetzen
+ function resetWorkflow() {
+ state.selectedFiles = [];
+ state.selectedAgents = [];
+ promptInput.value = "";
+ state.prompt = "";
+
+ renderFiles();
+ renderSelectedFiles();
+ renderAgents();
+ }
+
+ // Event-Listener für Menüpunkte
+ workflowLink.addEventListener('click', function(e) {
+ e.preventDefault();
+ setActiveView('workflow');
+ });
+
+ dataLink.addEventListener('click', function(e) {
+ e.preventDefault();
+ setActiveView('data');
+ });
+
+ promptsLink.addEventListener('click', function(e) {
+ e.preventDefault();
+ setActiveView('prompts');
+ });
+
+ agentsLink.addEventListener('click', function(e) {
+ e.preventDefault();
+ setActiveView('agents');
+ });
+
+ // Event-Listener
+ uploadBtn.addEventListener('click', () => fileInput.click());
+
+ fileInput.addEventListener('change', async (e) => {
+ if (e.target.files.length > 0) {
+ // Dateien hochladen
+ for (const file of e.target.files) {
+ try {
+ const newFile = await uploadFile(file);
+ if (newFile) {
+ state.files.push(newFile);
+ state.selectedFiles.push(newFile);
+ }
+ } catch (error) {
+ console.error("Fehler beim Hochladen der Datei:", error);
+ }
+ }
+
+ renderFiles();
+ renderSelectedFiles();
+ fileInput.value = "";
+ }
+ });
+
+ promptInput.addEventListener('input', (e) => {
+ state.prompt = e.target.value;
+ });
+
+ resetBtn.addEventListener('click', resetWorkflow);
+ startWorkflowBtn.addEventListener('click', runWorkflow);
+
+ // Initialisierung
+ async function init() {
+ // Daten vom Backend laden
+ await fetchWorkspaces();
+ await fetchFiles();
+
+ // UI initialisieren
+ renderFiles();
+ renderSelectedFiles();
+ renderAgents();
+ renderLogs();
+ renderResults();
+
+ // Initial die Standard-Ansicht setzen
+ setActiveView(activeView);
+ }
+
+ // Anwendung initialisieren
+ init();
+});
\ No newline at end of file
diff --git a/frontend/styles.css b/frontend/styles.css
new file mode 100644
index 00000000..1f419be2
--- /dev/null
+++ b/frontend/styles.css
@@ -0,0 +1,555 @@
+/* Grundlegende Resets und Fonts */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ background-color: #f0f2f5;
+ color: #333;
+ line-height: 1.6;
+}
+
+ul {
+ list-style: none;
+}
+
+a {
+ text-decoration: none;
+ color: inherit;
+}
+
+button {
+ cursor: pointer;
+ border: none;
+ background: none;
+ font-family: inherit;
+}
+
+/* Navbar */
+.navbar {
+ background-color: #2563eb;
+ color: white;
+ padding: 1rem;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.navbar-container {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ max-width: 1800px;
+ margin: 0 auto;
+}
+
+.navbar-logo {
+ font-size: 1.5rem;
+ font-weight: 600;
+}
+
+.navbar-user {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+.icon-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 2rem;
+ height: 2rem;
+ border-radius: 50%;
+ color: white;
+ transition: background-color 0.2s;
+}
+
+.icon-button:hover {
+ background-color: rgba(255, 255, 255, 0.2);
+}
+
+/* App Container Layout */
+.app-container {
+ display: flex;
+ max-width: 1800px;
+ margin: 0 auto;
+ min-height: calc(100vh - 4rem);
+}
+
+/* Sidebar */
+.sidebar {
+ width: 250px;
+ background-color: white;
+ box-shadow: 2px 0 5px rgba(0, 0, 0, 0.05);
+ padding: 1.5rem 1rem;
+ flex-shrink: 0;
+}
+
+.workspace-section h2 {
+ font-size: 1.2rem;
+ margin-bottom: 0.5rem;
+ font-weight: 600;
+}
+
+.workspace-list {
+ margin-bottom: 1.5rem;
+}
+
+.workspace-item {
+ padding: 0.5rem 0.75rem;
+ border-radius: 0.375rem;
+ margin-bottom: 0.25rem;
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+}
+
+.workspace-item i {
+ margin-right: 0.5rem;
+ color: #4b5563;
+}
+
+.workspace-item:hover {
+ background-color: #f3f4f6;
+}
+
+.workspace-item.active {
+ background-color: #e0f2fe;
+ color: #1d4ed8;
+}
+
+.sidebar-nav {
+ margin-top: 1rem;
+}
+
+.sidebar-item {
+ padding: 0.75rem;
+ border-radius: 0.375rem;
+ margin-bottom: 0.25rem;
+}
+
+.sidebar-item a {
+ display: flex;
+ align-items: center;
+}
+
+.sidebar-item i {
+ margin-right: 0.75rem;
+ width: 1.25rem;
+ text-align: center;
+}
+
+.sidebar-item:hover {
+ background-color: #f3f4f6;
+}
+
+.sidebar-item.active {
+ background-color: #e0f2fe;
+ color: #1d4ed8;
+}
+
+/* Main Content */
+.main-content {
+ flex: 1;
+ padding: 1.5rem;
+ overflow-y: auto;
+}
+
+.workflow-container {
+ display: flex;
+ gap: 1.5rem;
+}
+
+.config-panel, .results-panel {
+ flex: 1;
+}
+
+/* Cards */
+.card {
+ background-color: white;
+ border-radius: 0.5rem;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ padding: 1.25rem;
+ margin-bottom: 1.25rem;
+}
+
+h2 {
+ font-size: 1.5rem;
+ font-weight: 600;
+ margin-bottom: 1rem;
+}
+
+h3 {
+ font-size: 1.125rem;
+ font-weight: 600;
+ margin-bottom: 1rem;
+ color: #4b5563;
+}
+
+h4 {
+ font-size: 0.875rem;
+ font-weight: 600;
+ margin-bottom: 0.5rem;
+ color: #6b7280;
+}
+
+/* File Selection */
+.files-container {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 0.5rem;
+ font-weight: 500;
+}
+
+.upload-btn {
+ background-color: #fff;
+ border: 1px solid #d1d5db;
+ border-radius: 0.375rem;
+ padding: 0.375rem 0.75rem;
+ font-size: 0.875rem;
+ color: #4b5563;
+ display: flex;
+ align-items: center;
+ gap: 0.375rem;
+ transition: all 0.2s;
+}
+
+.upload-btn:hover {
+ background-color: #f9fafb;
+ border-color: #9ca3af;
+}
+
+.file-list {
+ max-height: 200px;
+ overflow-y: auto;
+ border: 1px solid #e5e7eb;
+ border-radius: 0.375rem;
+ background-color: #f9fafb;
+}
+
+.file-item {
+ padding: 0.625rem;
+ border-bottom: 1px solid #e5e7eb;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background-color: white;
+}
+
+.file-item:last-child {
+ border-bottom: none;
+}
+
+.file-item-name {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ flex: 1;
+}
+
+.file-item-info {
+ font-size: 0.75rem;
+ color: #6b7280;
+}
+
+.selected-files-container {
+ border: 1px dashed #d1d5db;
+ border-radius: 0.375rem;
+ min-height: 100px;
+ background-color: #f9fafb;
+}
+
+.empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100px;
+ color: #9ca3af;
+ gap: 0.5rem;
+}
+
+.empty-state i {
+ font-size: 1.5rem;
+}
+
+.selected-file-list {
+ padding: 0.5rem;
+}
+
+.selected-file-item {
+ background-color: white;
+ border: 1px solid #e5e7eb;
+ border-radius: 0.375rem;
+ padding: 0.5rem 0.75rem;
+ margin-bottom: 0.5rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.selected-file-item:last-child {
+ margin-bottom: 0;
+}
+
+.remove-file-btn {
+ color: #ef4444;
+ width: 1.5rem;
+ height: 1.5rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ transition: background-color 0.2s;
+}
+
+.remove-file-btn:hover {
+ background-color: #fee2e2;
+}
+
+/* Prompt input */
+#prompt-input {
+ width: 100%;
+ min-height: 150px;
+ border: 1px solid #d1d5db;
+ border-radius: 0.375rem;
+ padding: 0.75rem;
+ font-family: inherit;
+ resize: vertical;
+ font-size: 0.875rem;
+}
+
+#prompt-input:focus {
+ outline: none;
+ border-color: #2563eb;
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
+}
+
+/* Agent list */
+.agent-list {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.agent-item {
+ background-color: #f9fafb;
+ border: 1px solid #e5e7eb;
+ border-radius: 0.375rem;
+ padding: 0.75rem;
+}
+
+.agent-header {
+ display: flex;
+ align-items: center;
+}
+
+.agent-checkbox {
+ margin-right: 0.75rem;
+}
+
+.agent-name {
+ font-weight: 500;
+}
+
+.agent-description {
+ margin-top: 0.25rem;
+ font-size: 0.875rem;
+ color: #6b7280;
+ margin-left: 2rem;
+}
+
+/* Action buttons */
+.action-buttons {
+ display: flex;
+ justify-content: space-between;
+ gap: 0.75rem;
+}
+
+.reset-btn {
+ background-color: #f3f4f6;
+ color: #4b5563;
+ border-radius: 0.375rem;
+ padding: 0.625rem 1.25rem;
+ font-weight: 500;
+ transition: background-color 0.2s;
+}
+
+.reset-btn:hover {
+ background-color: #e5e7eb;
+}
+
+.start-btn {
+ background-color: #10b981;
+ color: white;
+ border-radius: 0.375rem;
+ padding: 0.625rem 1.25rem;
+ font-weight: 500;
+ transition: background-color 0.2s;
+}
+
+.start-btn:hover {
+ background-color: #059669;
+}
+
+.start-btn.running {
+ background-color: #9ca3af;
+ cursor: not-allowed;
+}
+
+/* Log Container */
+.log-container {
+ background-color: #111827;
+ color: #34d399;
+ border-radius: 0.375rem;
+ padding: 0.75rem;
+ font-family: 'Consolas', 'Monaco', monospace;
+ font-size: 0.875rem;
+ height: 300px;
+ overflow-y: auto;
+}
+
+.log-empty-state {
+ color: #6b7280;
+ font-style: italic;
+}
+
+.log-entry {
+ margin-bottom: 0.25rem;
+}
+
+.log-time {
+ color: #9ca3af;
+}
+
+.log-message {
+ margin-left: 0.5rem;
+}
+
+.log-info {
+ color: #ffffff;
+}
+
+.log-success {
+ color: #34d399;
+}
+
+.log-error {
+ color: #f87171;
+}
+
+.log-start {
+ color: #60a5fa;
+}
+
+.log-complete {
+ color: #c084fc;
+}
+
+/* Results Container */
+.results-container {
+ min-height: 200px;
+}
+
+.results-empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ background-color: #f9fafb;
+ border: 1px solid #e5e7eb;
+ border-radius: 0.375rem;
+ padding: 2.5rem 1rem;
+ color: #6b7280;
+ text-align: center;
+}
+
+.sub-text {
+ font-size: 0.875rem;
+ color: #9ca3af;
+ margin-top: 0.25rem;
+}
+
+.results-list {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.result-item {
+ background-color: #f9fafb;
+ border: 1px solid #e5e7eb;
+ border-radius: 0.375rem;
+ padding: 1rem;
+}
+
+.result-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ margin-bottom: 0.75rem;
+}
+
+.result-title {
+ font-weight: 600;
+ font-size: 1.125rem;
+}
+
+.result-agent {
+ font-size: 0.75rem;
+ color: #6b7280;
+}
+
+.save-btn {
+ display: flex;
+ align-items: center;
+ gap: 0.25rem;
+ color: #2563eb;
+ font-size: 0.875rem;
+ font-weight: 500;
+ padding: 0.25rem 0.5rem;
+ border-radius: 0.25rem;
+ transition: background-color 0.2s;
+}
+
+.save-btn:hover {
+ background-color: #e0f2fe;
+}
+
+.result-content {
+ background-color: white;
+ border: 1px solid #e5e7eb;
+ border-radius: 0.375rem;
+ padding: 0.75rem;
+ font-size: 0.875rem;
+ white-space: pre-line;
+}
+
+.chart-container {
+ background-color: white;
+ border: 1px solid #e5e7eb;
+ border-radius: 0.375rem;
+ padding: 0.75rem;
+ text-align: center;
+ height: 200px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #6b7280;
+}
+
+.chart-title {
+ border-bottom: 1px solid #e5e7eb;
+ padding-bottom: 0.5rem;
+ margin-bottom: 0.75rem;
+ font-weight: 500;
+}
\ No newline at end of file
diff --git a/backend/backend/__init__.py b/ida_prod/backend/backend/__init__.py
similarity index 100%
rename from backend/backend/__init__.py
rename to ida_prod/backend/backend/__init__.py
diff --git a/backend/backend/asgi.py b/ida_prod/backend/backend/asgi.py
similarity index 100%
rename from backend/backend/asgi.py
rename to ida_prod/backend/backend/asgi.py
diff --git a/backend/backend/settings.py b/ida_prod/backend/backend/settings.py
similarity index 100%
rename from backend/backend/settings.py
rename to ida_prod/backend/backend/settings.py
diff --git a/backend/backend/urls.py b/ida_prod/backend/backend/urls.py
similarity index 100%
rename from backend/backend/urls.py
rename to ida_prod/backend/backend/urls.py
diff --git a/backend/backend/views.py b/ida_prod/backend/backend/views.py
similarity index 100%
rename from backend/backend/views.py
rename to ida_prod/backend/backend/views.py
diff --git a/backend/backend/wsgi.py b/ida_prod/backend/backend/wsgi.py
similarity index 100%
rename from backend/backend/wsgi.py
rename to ida_prod/backend/backend/wsgi.py
diff --git a/backend/manage.py b/ida_prod/backend/manage.py
similarity index 100%
rename from backend/manage.py
rename to ida_prod/backend/manage.py
diff --git a/frontend/frontend/.gitignore b/ida_prod/frontend/frontend/.gitignore
similarity index 100%
rename from frontend/frontend/.gitignore
rename to ida_prod/frontend/frontend/.gitignore
diff --git a/frontend/frontend/README.md b/ida_prod/frontend/frontend/README.md
similarity index 100%
rename from frontend/frontend/README.md
rename to ida_prod/frontend/frontend/README.md
diff --git a/frontend/frontend/package-lock.json b/ida_prod/frontend/frontend/package-lock.json
similarity index 100%
rename from frontend/frontend/package-lock.json
rename to ida_prod/frontend/frontend/package-lock.json
diff --git a/frontend/frontend/package.json b/ida_prod/frontend/frontend/package.json
similarity index 100%
rename from frontend/frontend/package.json
rename to ida_prod/frontend/frontend/package.json
diff --git a/frontend/frontend/public/favicon.ico b/ida_prod/frontend/frontend/public/favicon.ico
similarity index 100%
rename from frontend/frontend/public/favicon.ico
rename to ida_prod/frontend/frontend/public/favicon.ico
diff --git a/frontend/frontend/public/index.html b/ida_prod/frontend/frontend/public/index.html
similarity index 100%
rename from frontend/frontend/public/index.html
rename to ida_prod/frontend/frontend/public/index.html
diff --git a/frontend/frontend/public/logo192.png b/ida_prod/frontend/frontend/public/logo192.png
similarity index 100%
rename from frontend/frontend/public/logo192.png
rename to ida_prod/frontend/frontend/public/logo192.png
diff --git a/frontend/frontend/public/logo512.png b/ida_prod/frontend/frontend/public/logo512.png
similarity index 100%
rename from frontend/frontend/public/logo512.png
rename to ida_prod/frontend/frontend/public/logo512.png
diff --git a/frontend/frontend/public/manifest.json b/ida_prod/frontend/frontend/public/manifest.json
similarity index 100%
rename from frontend/frontend/public/manifest.json
rename to ida_prod/frontend/frontend/public/manifest.json
diff --git a/frontend/frontend/public/robots.txt b/ida_prod/frontend/frontend/public/robots.txt
similarity index 100%
rename from frontend/frontend/public/robots.txt
rename to ida_prod/frontend/frontend/public/robots.txt
diff --git a/frontend/frontend/src/App.css b/ida_prod/frontend/frontend/src/App.css
similarity index 100%
rename from frontend/frontend/src/App.css
rename to ida_prod/frontend/frontend/src/App.css
diff --git a/frontend/frontend/src/App.js b/ida_prod/frontend/frontend/src/App.js
similarity index 100%
rename from frontend/frontend/src/App.js
rename to ida_prod/frontend/frontend/src/App.js
diff --git a/frontend/frontend/src/App.test.js b/ida_prod/frontend/frontend/src/App.test.js
similarity index 100%
rename from frontend/frontend/src/App.test.js
rename to ida_prod/frontend/frontend/src/App.test.js
diff --git a/frontend/frontend/src/api.js b/ida_prod/frontend/frontend/src/api.js
similarity index 100%
rename from frontend/frontend/src/api.js
rename to ida_prod/frontend/frontend/src/api.js
diff --git a/frontend/frontend/src/index.css b/ida_prod/frontend/frontend/src/index.css
similarity index 100%
rename from frontend/frontend/src/index.css
rename to ida_prod/frontend/frontend/src/index.css
diff --git a/frontend/frontend/src/index.js b/ida_prod/frontend/frontend/src/index.js
similarity index 100%
rename from frontend/frontend/src/index.js
rename to ida_prod/frontend/frontend/src/index.js
diff --git a/frontend/frontend/src/logo.svg b/ida_prod/frontend/frontend/src/logo.svg
similarity index 100%
rename from frontend/frontend/src/logo.svg
rename to ida_prod/frontend/frontend/src/logo.svg
diff --git a/frontend/frontend/src/reportWebVitals.js b/ida_prod/frontend/frontend/src/reportWebVitals.js
similarity index 100%
rename from frontend/frontend/src/reportWebVitals.js
rename to ida_prod/frontend/frontend/src/reportWebVitals.js
diff --git a/frontend/frontend/src/setupTests.js b/ida_prod/frontend/frontend/src/setupTests.js
similarity index 100%
rename from frontend/frontend/src/setupTests.js
rename to ida_prod/frontend/frontend/src/setupTests.js
diff --git a/ida_prod/requirements.txt b/ida_prod/requirements.txt
new file mode 100644
index 00000000..9ecd9896
--- /dev/null
+++ b/ida_prod/requirements.txt
@@ -0,0 +1,9 @@
+Django
+gunicorn
+django-cors-headers
+djangorestframework
+whitenoise
+openai
+Flask
+requests
+gunicorn
\ No newline at end of file
diff --git a/ida_prod/start.sh b/ida_prod/start.sh
new file mode 100644
index 00000000..276092ae
--- /dev/null
+++ b/ida_prod/start.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+# Installiere Abhängigkeiten
+cd backend
+python -m venv venv
+source venv/bin/activate
+pip install -r requirements.txt
+
+# Migration durchführen
+python manage.py migrate
+python manage.py collectstatic --noinput
+
+# React bauen
+cd ../frontend
+npm install
+npm run build
+
+# React-Frontend nach Django-Statisches-Verzeichnis verschieben
+cp -r build/* ../backend/staticfiles/
+
+# Starte Gunicorn (Django-Server)
+cd ../backend
+gunicorn backend.wsgi --bind 0.0.0.0:8000
diff --git a/readme.md b/readme.md
new file mode 100644
index 00000000..525d7f72
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,168 @@
+# Data Platform - Multi-Agent Service
+
+Eine Full-Stack-Webapplikation für die Ausführung von Multi-Agent-Workflows zur Verarbeitung und Analyse von Daten basierend auf natürlichsprachlichen Benutzeranfragen.
+
+Hier: http://localhost:8000/docs
+
+## Übersicht
+
+Das System ermöglicht Benutzern:
+- Hochladen und Verwalten verschiedener Datendateien
+- Definieren von Prompts/Anweisungen für KI-Agenten
+- Auswählen und Kombinieren spezialisierter Agenten
+- Ausführen von Workflows mit Echtzeit-Protokollierung
+- Visualisieren und Verwalten der Ergebnisse
+
+## Projektstruktur
+
+Das Projekt besteht aus zwei Hauptkomponenten:
+
+### Frontend (HTML/CSS/JavaScript)
+
+- `index.html` - Hauptstruktur der Benutzeroberfläche
+- `styles.css` - Umfangreiches CSS für das responsive Design
+- `script.js` - Client-seitige Logik für Interaktionen
+
+### Backend (Python/FastAPI)
+
+- `app.py` - Hauptanwendung mit API-Endpunkten
+- `models.py` - Datenmodelle und Validierungsschemas
+- `database.py` - Datenpersistenz (JSON-basiert für Demo)
+- `agent_service.py` - Multi-Agent-Orchestrierung
+- `requirements.txt` - Python-Abhängigkeiten
+
+## Hauptfunktionen
+
+### Workspace-Management
+- Mehrere Workspaces für verschiedene Projekte
+- Organisierte Gruppenarbeit mit geteilten Ressourcen
+
+### Datei-Verarbeitung
+- Upload verschiedener Dateitypen (PDF, Excel, Bilder, etc.)
+- Automatische Erkennung und Kategorisierung
+
+### Agent-Orchestrierung
+- Kombination verschiedener Agent-Typen:
+ - **Datenanalyse-Agent**: Extrahiert Insights aus strukturierten Daten
+ - **Visualisierungs-Agent**: Erstellt Diagramme und visuelle Darstellungen
+ - **Text-Generator**: Verfasst Berichte und Zusammenfassungen
+ - **Web-Scraper**: Sammelt externe Daten
+ - **Marktanalyse-Agent**: Spezialisiert auf Wettbewerbsanalyse
+
+### Workflow-Ausführung
+- Echtzeit-Protokollierung des Fortschritts
+- Vollständige Nachverfolgbarkeit aller Schritte
+
+### Ergebnis-Management
+- Strukturierte Darstellung von Analysen, Diagrammen und Berichten
+- Export- und Sharing-Funktionen
+
+## Installation und Einrichtung
+
+### Voraussetzungen
+- Python 3.8+
+- Ein moderner Webbrowser
+- Optional: Node.js für Entwicklungswerkzeuge
+
+### Frontend-Installation
+1. Klonen des Repositories
+2. Platzieren der Frontend-Dateien auf einem Webserver oder lokalen Entwicklungsserver:
+ ```bash
+ # Mit Python einen einfachen HTTP-Server starten
+ python -m http.server 8080
+ ```
+
+### Backend-Installation
+1. Virtuelle Umgebung erstellen und aktivieren:
+ ```bash
+ python -m venv venv
+ source venv/bin/activate # Linux/Mac
+ venv\Scripts\activate # Windows
+ ```
+
+2. Abhängigkeiten installieren:
+ ```bash
+ pip install -r requirements.txt
+ ```
+
+3. Server starten:
+ ```bash
+ uvicorn app:app --reload --host 0.0.0.0 --port 8000
+ ```
+
+4. Zugangspunkte:
+ - Frontend: `http://localhost:8080`
+ - Backend API: `http://localhost:8000`
+ - API-Dokumentation: `http://localhost:8000/docs`
+
+## Verwendung: Der Customer Journey
+
+### 1. Workspace auswählen oder erstellen
+- Wählen Sie einen vorhandenen Workspace oder erstellen Sie einen neuen für Ihr Projekt
+
+### 2. Dateien hochladen
+- Laden Sie die zu analysierenden Dateien hoch
+- Das System erkennt automatisch Dateitypen und bereitet sie für die Verarbeitung vor
+
+### 3. Prompt formulieren
+- Definieren Sie in natürlicher Sprache, was Sie analysieren möchten
+- Je präziser Ihre Anweisungen, desto zielgerichteter die Ergebnisse
+
+### 4. Agenten konfigurieren
+- Wählen Sie die passenden Agenten für Ihre Aufgabe
+- Kombinieren Sie Agenten für umfassendere Analysen
+ - Datenanalyse → Visualisierung → Textgenerierung
+
+### 5. Workflow ausführen
+- Starten Sie den Workflow und verfolgen Sie die Ausführung in Echtzeit
+- Im linken Bereich sehen Sie die Konfiguration
+- Im rechten Bereich werden Protokoll und Ergebnisse angezeigt
+
+### 6. Ergebnisse verwenden
+- Sehen Sie Analysen, Diagramme und Berichte ein
+- Exportieren oder teilen Sie die Ergebnisse
+- Iterieren Sie bei Bedarf mit angepassten Prompts oder Agent-Konfigurationen
+
+## Anpassung und Erweiterung
+
+### Integration mit echten KI-Diensten
+Die aktuelle Implementierung simuliert die Agent-Verarbeitung. Für eine produktive Nutzung:
+
+1. Erweitern Sie `agent_service.py` mit Integrationen zu:
+ - OpenAI GPT-Modellen (ChatGPT, GPT-4)
+ - Claude von Anthropic
+ - Eigenentwickelten Modellen mit spezieller Expertise
+
+2. Implementieren Sie robuste Datei-Parser für:
+ - PDF-Dokumente mit OCR
+ - Excel- und CSV-Verarbeitung
+ - Bild- und Medienanalyse
+
+3. Ergänzen Sie Authentifizierung und Autorisierung:
+ - Benutzer-Accounts mit Rollenkonzept
+ - API-Schlüsselverwaltung für externe Dienste
+ - Sichere Datenspeicherung
+
+### Datenbank-Migration
+Für größere Installationen die JSON-basierte Datenbank ersetzen durch:
+- PostgreSQL für relationale Daten
+- MongoDB für Dokumente und unstrukturierte Daten
+- Redis für Caching und Workflow-Status
+
+## Technische Details
+
+### Frontend-Architektur
+- Vanilla JavaScript ohne Framework-Abhängigkeiten
+- Modularer CSS-Ansatz für einfache Anpassungen
+- Responsive Design für Desktop und mobile Nutzung
+
+### Backend-Architektur
+- FastAPI für hohe Performance und automatische API-Dokumentation
+- Asynchrone Verarbeitung für parallele Agent-Ausführung
+- Erweiterbare Service-Struktur für einfache Integration neuer Agententypen
+
+## Lizenz
+
+PRIVATE LICENSE PATRICK MOTSCH ValueOn AG
+---
+Für Fragen oder Unterstützung wenden Sie sich bitte an p.motsch@valueon.ch
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 9ecd9896..79715519 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,9 +1,11 @@
-Django
-gunicorn
-django-cors-headers
-djangorestframework
-whitenoise
-openai
-Flask
-requests
-gunicorn
\ No newline at end of file
+fastapi==0.103.1
+uvicorn==0.23.2
+python-multipart==0.0.6
+pydantic==2.4.2
+typing-extensions==4.8.0
+python-dateutil==2.8.2
+six==1.16.0
+starlette==0.27.0
+anyio==3.7.1
+idna==3.4
+sniffio==1.3.0
\ No newline at end of file
diff --git a/start.bat b/start.bat
new file mode 100644
index 00000000..1a7c2e65
--- /dev/null
+++ b/start.bat
@@ -0,0 +1,53 @@
+@echo off
+echo Data Platform - Multi-Agent Service
+echo Startskript fuer Frontend und Backend
+echo ----------------------------------------
+
+:: Verzeichnisstruktur erstellen, falls sie nicht existiert
+if not exist backend\data mkdir backend\data
+if not exist backend\uploads mkdir backend\uploads
+if not exist backend\results mkdir backend\results
+if not exist backend\static mkdir backend\static
+
+:: Prüfen, ob Python installiert ist
+python --version >nul 2>&1
+if %errorlevel% neq 0 (
+ echo Python ist nicht installiert. Bitte installieren Sie Python 3.8 oder hoeher.
+ exit /b 1
+)
+
+:: Virtuelle Umgebung erstellen, falls sie nicht existiert
+if not exist backend\venv (
+ echo Erstelle virtuelle Python-Umgebung...
+ cd backend
+ python -m venv venv
+ cd ..
+)
+
+:: Virtuelle Umgebung aktivieren
+echo Aktiviere virtuelle Umgebung...
+call backend\venv\Scripts\activate
+
+:: Abhängigkeiten installieren
+echo Installiere Abhaengigkeiten...
+pip install -r requirements.txt
+
+:: Starte Backend in neuem Fenster
+echo Starte Backend-Server...
+start cmd /k "cd backend && call venv\Scripts\activate && uvicorn app:app --reload --host 0.0.0.0 --port 8000"
+
+:: Kurz warten, um sicherzustellen, dass das Backend startet
+timeout /t 2 >nul
+
+:: Starte Frontend-Server in neuem Fenster
+echo Starte Frontend-Server...
+start cmd /k "cd frontend && python -m http.server 8080"
+
+echo ----------------------------------------
+echo Server wurden gestartet!
+echo Frontend laeuft auf: http://localhost:8080
+echo Backend API laeuft auf: http://localhost:8000
+echo API-Dokumentation: http://localhost:8000/docs
+echo Schliesse die Kommandozeilenfenster, um die Server zu beenden.
+
+pause
\ No newline at end of file
diff --git a/start.sh b/start.sh
index 276092ae..2a705b27 100644
--- a/start.sh
+++ b/start.sh
@@ -1,22 +1,81 @@
#!/bin/bash
-# Installiere Abhängigkeiten
-cd backend
-python -m venv venv
-source venv/bin/activate
+
+# Farben für die Ausgabe
+GREEN='\033[0;32m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+echo -e "${GREEN}Data Platform - Multi-Agent Service${NC}"
+echo -e "${BLUE}Startskript für Frontend und Backend${NC}"
+echo "----------------------------------------"
+
+# Verzeichnisstruktur erstellen, falls sie nicht existiert
+mkdir -p backend/data
+mkdir -p backend/uploads
+mkdir -p backend/results
+mkdir -p backend/static
+
+# Prüfen, ob Python installiert ist
+if command -v python3 &>/dev/null; then
+ PYTHON_CMD="python3"
+elif command -v python &>/dev/null; then
+ PYTHON_CMD="python"
+else
+ echo "Python ist nicht installiert. Bitte installieren Sie Python 3.8 oder höher."
+ exit 1
+fi
+
+# Virtuelle Umgebung erstellen, falls sie nicht existiert
+if [ ! -d "backend/venv" ]; then
+ echo "Erstelle virtuelle Python-Umgebung..."
+ cd backend
+ $PYTHON_CMD -m venv venv
+ cd ..
+fi
+
+# Virtuelle Umgebung aktivieren
+echo "Aktiviere virtuelle Umgebung..."
+source backend/venv/bin/activate 2>/dev/null || . backend/venv/bin/activate
+
+# Abhängigkeiten installieren
+echo "Installiere Abhängigkeiten..."
pip install -r requirements.txt
-# Migration durchführen
-python manage.py migrate
-python manage.py collectstatic --noinput
+# Backend als Hintergrundprozess starten
+echo "Starte Backend-Server..."
+cd backend
+uvicorn app:app --reload --host 0.0.0.0 --port 8000 &
+BACKEND_PID=$!
+cd ..
-# React bauen
-cd ../frontend
-npm install
-npm run build
+# Kurz warten, um sicherzustellen, dass das Backend startet
+sleep 2
-# React-Frontend nach Django-Statisches-Verzeichnis verschieben
-cp -r build/* ../backend/staticfiles/
+# Frontend-Server starten
+echo "Starte Frontend-Server..."
+cd frontend
+$PYTHON_CMD -m http.server 8080 &
+FRONTEND_PID=$!
+cd ..
-# Starte Gunicorn (Django-Server)
-cd ../backend
-gunicorn backend.wsgi --bind 0.0.0.0:8000
+echo "----------------------------------------"
+echo -e "${GREEN}Server wurden gestartet!${NC}"
+echo "Frontend läuft auf: http://localhost:8080"
+echo "Backend API läuft auf: http://localhost:8000"
+echo "API-Dokumentation: http://localhost:8000/docs"
+echo -e "${BLUE}Drücke STRG+C, um beide Server zu beenden${NC}"
+
+# Funktion zum Beenden der Server bei STRG+C
+cleanup() {
+ echo -e "\n${GREEN}Beende Server...${NC}"
+ kill $BACKEND_PID
+ kill $FRONTEND_PID
+ echo "Server wurden beendet"
+ exit 0
+}
+
+# Signal-Handler für STRG+C
+trap cleanup SIGINT
+
+# Warten auf Benutzeraktion
+wait
\ No newline at end of file
diff --git a/test/data.py b/test/data.py
new file mode 100644
index 00000000..a80f76eb
--- /dev/null
+++ b/test/data.py
@@ -0,0 +1,55 @@
+data_object_model = {
+ "Mandate": {
+ "id": "mandate_001",
+ "users": [
+ {
+ "id": "user_001",
+ "settings": {
+ "id": "setting_001",
+ "preferences": {}
+ },
+ "sessions": [
+ {
+ "id": "session_001",
+ "timestamp": "2025-03-13T12:00:00Z",
+ "active": True
+ }
+ ],
+ "workspaces": [
+ {
+ "id": "workspace_001",
+ "prompts": [
+ {
+ "id": "prompt_001",
+ "content": "",
+ "created_at": "2025-03-13T10:30:00Z"
+ }
+ ],
+ "agents": [
+ {
+ "id": "agent_001",
+ "type": "assistant",
+ "capabilities": []
+ }
+ ],
+ "dataObjectReferences": ["dataobject_001", "dataobject_002"]
+ }
+ ],
+ "dataObjects": [
+ {
+ "id": "dataobject_001",
+ "type": "document",
+ "content": {},
+ "metadata": {}
+ },
+ {
+ "id": "dataobject_002",
+ "type": "image",
+ "content": {},
+ "metadata": {}
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/main.py b/test/gw_main.py
similarity index 100%
rename from main.py
rename to test/gw_main.py
diff --git a/test.py b/test/gw_test.py
similarity index 100%
rename from test.py
rename to test/gw_test.py