test3
162
.gitignore
vendored
Normal file
|
|
@ -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/
|
||||||
255
backend/agent_service.py
Normal file
|
|
@ -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
|
||||||
305
backend/app.py
Normal file
|
|
@ -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)
|
||||||
4
backend/data/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
||||||
299
backend/database.py
Normal file
|
|
@ -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
|
||||||
80
backend/models.py
Normal file
|
|
@ -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
|
||||||
4
backend/results/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
||||||
4
backend/static/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
||||||
4
backend/uploads/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
REACT_APP_API_URL=https://volucy-gateway-e3d2bzbxdeaaayhz.westeurope-01.azurewebsites.net
|
|
||||||
204
frontend/index.html
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Data Platform - Multi-Agent Service</title>
|
||||||
|
<link rel="stylesheet" href="./styles.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="navbar">
|
||||||
|
<div class="navbar-container">
|
||||||
|
<h1 class="navbar-logo">Data Platform</h1>
|
||||||
|
<div class="navbar-user">
|
||||||
|
<span>Demo User</span>
|
||||||
|
<button class="icon-button">
|
||||||
|
<i class="fas fa-cog"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- Seitenleiste -->
|
||||||
|
<aside class="sidebar">
|
||||||
|
<div class="workspace-section">
|
||||||
|
<h2>Workspaces</h2>
|
||||||
|
<ul class="workspace-list" id="workspace-list">
|
||||||
|
<!-- Workspace-Liste wird vom JavaScript gefüllt -->
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="sidebar-nav">
|
||||||
|
<li class="sidebar-item active">
|
||||||
|
<a href="#workflow">
|
||||||
|
<i class="fas fa-play-circle"></i>
|
||||||
|
<span>Workflow-Ausführung</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="sidebar-item">
|
||||||
|
<a href="#data">
|
||||||
|
<i class="fas fa-database"></i>
|
||||||
|
<span>Meine Daten</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="sidebar-item">
|
||||||
|
<a href="#prompts">
|
||||||
|
<i class="fas fa-comment-alt"></i>
|
||||||
|
<span>Meine Prompts</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="sidebar-item">
|
||||||
|
<a href="#agents">
|
||||||
|
<i class="fas fa-wrench"></i>
|
||||||
|
<span>Meine Agents</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Hauptinhalt -->
|
||||||
|
<main class="main-content">
|
||||||
|
<div class="workflow-container">
|
||||||
|
|
||||||
|
<!-- Meine Prompts Ansicht -->
|
||||||
|
<div id="prompts-view" style="display:none;">
|
||||||
|
<h2>Meine Prompts</h2>
|
||||||
|
<div class="card">
|
||||||
|
<h3>Gespeicherte Prompts</h3>
|
||||||
|
<div id="prompts-list">
|
||||||
|
<!-- Wird dynamisch gefüllt -->
|
||||||
|
</div>
|
||||||
|
<button class="add-btn" id="add-prompt-btn">
|
||||||
|
<i class="fas fa-plus"></i> Neuen Prompt erstellen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Meine Agents Ansicht -->
|
||||||
|
<div id="agents-view" style="display:none;">
|
||||||
|
<h2>Meine Agents</h2>
|
||||||
|
<div class="card">
|
||||||
|
<h3>Verfügbare Agents</h3>
|
||||||
|
<div id="agents-list-container">
|
||||||
|
<!-- Wird dynamisch gefüllt -->
|
||||||
|
</div>
|
||||||
|
<button class="add-btn" id="add-agent-btn">
|
||||||
|
<i class="fas fa-plus"></i> Neuen Agent hinzufügen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Linke Seite - Workflow-Konfiguration -->
|
||||||
|
<div class="config-panel">
|
||||||
|
<h2>Workflow-Konfiguration</h2>
|
||||||
|
|
||||||
|
<!-- 1. Dateien auswählen -->
|
||||||
|
<div class="card">
|
||||||
|
<h3>1. Dateien auswählen</h3>
|
||||||
|
|
||||||
|
<div class="files-container">
|
||||||
|
<div class="file-selection">
|
||||||
|
<div class="section-header">
|
||||||
|
<span>Verfügbare Dateien</span>
|
||||||
|
<button class="upload-btn" id="upload-btn">
|
||||||
|
<i class="fas fa-upload"></i> Hochladen
|
||||||
|
</button>
|
||||||
|
<input type="file" id="file-input" multiple hidden>
|
||||||
|
</div>
|
||||||
|
<ul class="file-list" id="available-files">
|
||||||
|
<!-- Wird dynamisch gefüllt -->
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="selected-files">
|
||||||
|
<h4>Ausgewählte Dateien</h4>
|
||||||
|
<div class="selected-files-container" id="selected-files-container">
|
||||||
|
<div class="empty-state" id="empty-files-state">
|
||||||
|
<i class="fas fa-upload"></i>
|
||||||
|
<span>Keine Dateien ausgewählt</span>
|
||||||
|
</div>
|
||||||
|
<ul class="selected-file-list" id="selected-files">
|
||||||
|
<!-- Wird dynamisch gefüllt -->
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 2. Prompt eingeben -->
|
||||||
|
<div class="card">
|
||||||
|
<h3>2. Prompt eingeben</h3>
|
||||||
|
<textarea
|
||||||
|
id="prompt-input"
|
||||||
|
placeholder="Beschreiben Sie die Aufgabe für die Agenten..."
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 3. Agenten auswählen -->
|
||||||
|
<div class="card">
|
||||||
|
<h3>3. Agenten auswählen</h3>
|
||||||
|
<ul class="agent-list" id="agent-list">
|
||||||
|
<!-- Wird dynamisch gefüllt -->
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Aktionsbuttons -->
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button class="reset-btn" id="reset-btn">Zurücksetzen</button>
|
||||||
|
<button class="start-btn" id="start-workflow-btn">Workflow starten</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Rechte Seite - Ausführung und Ergebnisse -->
|
||||||
|
<div class="results-panel">
|
||||||
|
<h2>Ausführung & Ergebnisse</h2>
|
||||||
|
|
||||||
|
<!-- Ausführungsprotokoll -->
|
||||||
|
<div class="card">
|
||||||
|
<h3>Ausführungsprotokoll</h3>
|
||||||
|
<div class="log-container" id="execution-log">
|
||||||
|
<div class="log-empty-state">Workflow noch nicht gestartet. Protokoll wird hier angezeigt.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Ergebnisse -->
|
||||||
|
<div class="card">
|
||||||
|
<h3>Ergebnisse</h3>
|
||||||
|
<div class="results-container" id="results-container">
|
||||||
|
<div class="results-empty-state" id="empty-results-state">
|
||||||
|
<div>Keine Ergebnisse verfügbar</div>
|
||||||
|
<div class="sub-text">Führen Sie einen Workflow aus, um Ergebnisse zu sehen</div>
|
||||||
|
</div>
|
||||||
|
<div class="results-list" id="results-list">
|
||||||
|
<!-- Wird dynamisch gefüllt -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Meine Daten Ansicht -->
|
||||||
|
<div id="data-view" style="display:none;">
|
||||||
|
<h2>Meine Daten</h2>
|
||||||
|
<div class="card">
|
||||||
|
<h3>Verfügbare Dateien</h3>
|
||||||
|
<div class="files-container">
|
||||||
|
<ul class="file-list" id="my-files-list">
|
||||||
|
<!-- Wird dynamisch gefüllt -->
|
||||||
|
</ul>
|
||||||
|
<button class="upload-btn" id="upload-files-btn">
|
||||||
|
<i class="fas fa-upload"></i> Neue Dateien hochladen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
654
frontend/script.js
Normal file
|
|
@ -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 = `
|
||||||
|
<i class="fas fa-folder"></i>
|
||||||
|
<span>${workspace.name}</span>
|
||||||
|
`;
|
||||||
|
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 = `
|
||||||
|
<div class="file-item-name">
|
||||||
|
<input type="checkbox" id="file-${file.id}" ${isSelected ? 'checked' : ''}>
|
||||||
|
<label for="file-${file.id}">
|
||||||
|
<i class="fas ${file.type === 'document' ? 'fa-file-alt' : 'fa-file-image'}"></i>
|
||||||
|
${file.name}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<span class="file-item-info">${file.size || ''}</span>
|
||||||
|
`;
|
||||||
|
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 = `
|
||||||
|
<div class="file-item-name">
|
||||||
|
<i class="fas ${file.type === 'document' ? 'fa-file-alt' : 'fa-file-image'}"></i>
|
||||||
|
${file.name}
|
||||||
|
</div>
|
||||||
|
<button class="remove-file-btn" data-id="${file.id}">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
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 = '<div class="empty-state">Keine Prompts im aktuellen Workspace verfügbar.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.currentWorkspace.prompts.forEach(prompt => {
|
||||||
|
const promptItem = document.createElement('div');
|
||||||
|
promptItem.className = 'prompt-item';
|
||||||
|
promptItem.innerHTML = `
|
||||||
|
<div class="prompt-content">${prompt.content}</div>
|
||||||
|
<div class="prompt-meta">Erstellt am: ${new Date(prompt.created_at).toLocaleString()}</div>
|
||||||
|
<div class="prompt-actions">
|
||||||
|
<button class="use-prompt-btn" data-id="${prompt.id}">
|
||||||
|
<i class="fas fa-play"></i> Verwenden
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 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 = '<div class="empty-state">Keine Agenten im aktuellen Workspace verfügbar.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.currentWorkspace.agents.forEach(agent => {
|
||||||
|
const agentItem = document.createElement('div');
|
||||||
|
agentItem.className = 'agent-list-item';
|
||||||
|
agentItem.innerHTML = `
|
||||||
|
<div class="agent-header">
|
||||||
|
<h4>${agent.name}</h4>
|
||||||
|
<span class="agent-type">${agent.type}</span>
|
||||||
|
</div>
|
||||||
|
<div class="agent-description">${agent.description || ''}</div>
|
||||||
|
<div class="agent-capabilities">
|
||||||
|
${agent.capabilities ? agent.capabilities.map(cap => `<span class="capability-tag">${cap}</span>`).join('') : ''}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
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 = '<div class="empty-state">Keine Agenten im ausgewählten Workspace verfügbar.</div>';
|
||||||
|
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 = `
|
||||||
|
<div class="agent-header">
|
||||||
|
<input type="checkbox" id="agent-${agent.id}" class="agent-checkbox" ${isSelected ? 'checked' : ''}>
|
||||||
|
<label for="agent-${agent.id}" class="agent-name">${agent.name}</label>
|
||||||
|
</div>
|
||||||
|
<div class="agent-description">${agent.description || ''}</div>
|
||||||
|
`;
|
||||||
|
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 = '<div class="log-empty-state">Workflow noch nicht gestartet. Protokoll wird hier angezeigt.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
executionLog.innerHTML = '';
|
||||||
|
state.logs.forEach(log => {
|
||||||
|
const logEntry = document.createElement('div');
|
||||||
|
logEntry.className = 'log-entry';
|
||||||
|
logEntry.innerHTML = `
|
||||||
|
<span class="log-time">[${new Date(log.timestamp).toLocaleTimeString()}]</span>
|
||||||
|
<span class="log-message log-${log.type}">${log.message}</span>
|
||||||
|
`;
|
||||||
|
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 = `
|
||||||
|
<div class="result-header">
|
||||||
|
<div>
|
||||||
|
<div class="result-title">${result.title}</div>
|
||||||
|
<div class="result-agent">Erstellt von: ${result.agent_name}</div>
|
||||||
|
</div>
|
||||||
|
<button class="save-btn">
|
||||||
|
<i class="fas fa-save"></i> Speichern
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="result-content">${result.content}</div>
|
||||||
|
`;
|
||||||
|
} else if (result.type === 'chart') {
|
||||||
|
resultItem.innerHTML = `
|
||||||
|
<div class="result-header">
|
||||||
|
<div>
|
||||||
|
<div class="result-title">${result.title}</div>
|
||||||
|
<div class="result-agent">Erstellt von: ${result.agent_name}</div>
|
||||||
|
</div>
|
||||||
|
<button class="save-btn">
|
||||||
|
<i class="fas fa-save"></i> Speichern
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="chart-container">
|
||||||
|
<div class="chart-title">Umsatzentwicklung nach Quartal</div>
|
||||||
|
<div>${result.content}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
555
frontend/styles.css
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
9
ida_prod/requirements.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
Django
|
||||||
|
gunicorn
|
||||||
|
django-cors-headers
|
||||||
|
djangorestframework
|
||||||
|
whitenoise
|
||||||
|
openai
|
||||||
|
Flask
|
||||||
|
requests
|
||||||
|
gunicorn
|
||||||
22
ida_prod/start.sh
Normal file
|
|
@ -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
|
||||||
168
readme.md
Normal file
|
|
@ -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
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
Django
|
fastapi==0.103.1
|
||||||
gunicorn
|
uvicorn==0.23.2
|
||||||
django-cors-headers
|
python-multipart==0.0.6
|
||||||
djangorestframework
|
pydantic==2.4.2
|
||||||
whitenoise
|
typing-extensions==4.8.0
|
||||||
openai
|
python-dateutil==2.8.2
|
||||||
Flask
|
six==1.16.0
|
||||||
requests
|
starlette==0.27.0
|
||||||
gunicorn
|
anyio==3.7.1
|
||||||
|
idna==3.4
|
||||||
|
sniffio==1.3.0
|
||||||
53
start.bat
Normal file
|
|
@ -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
|
||||||
89
start.sh
|
|
@ -1,22 +1,81 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Installiere Abhängigkeiten
|
|
||||||
|
# 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
|
cd backend
|
||||||
python -m venv venv
|
$PYTHON_CMD -m venv venv
|
||||||
source venv/bin/activate
|
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
|
pip install -r requirements.txt
|
||||||
|
|
||||||
# Migration durchführen
|
# Backend als Hintergrundprozess starten
|
||||||
python manage.py migrate
|
echo "Starte Backend-Server..."
|
||||||
python manage.py collectstatic --noinput
|
cd backend
|
||||||
|
uvicorn app:app --reload --host 0.0.0.0 --port 8000 &
|
||||||
|
BACKEND_PID=$!
|
||||||
|
cd ..
|
||||||
|
|
||||||
# React bauen
|
# Kurz warten, um sicherzustellen, dass das Backend startet
|
||||||
cd ../frontend
|
sleep 2
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
# React-Frontend nach Django-Statisches-Verzeichnis verschieben
|
# Frontend-Server starten
|
||||||
cp -r build/* ../backend/staticfiles/
|
echo "Starte Frontend-Server..."
|
||||||
|
cd frontend
|
||||||
|
$PYTHON_CMD -m http.server 8080 &
|
||||||
|
FRONTEND_PID=$!
|
||||||
|
cd ..
|
||||||
|
|
||||||
# Starte Gunicorn (Django-Server)
|
echo "----------------------------------------"
|
||||||
cd ../backend
|
echo -e "${GREEN}Server wurden gestartet!${NC}"
|
||||||
gunicorn backend.wsgi --bind 0.0.0.0:8000
|
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
|
||||||
55
test/data.py
Normal file
|
|
@ -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": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||