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
|
||||
gunicorn
|
||||
django-cors-headers
|
||||
djangorestframework
|
||||
whitenoise
|
||||
openai
|
||||
Flask
|
||||
requests
|
||||
gunicorn
|
||||
fastapi==0.103.1
|
||||
uvicorn==0.23.2
|
||||
python-multipart==0.0.6
|
||||
pydantic==2.4.2
|
||||
typing-extensions==4.8.0
|
||||
python-dateutil==2.8.2
|
||||
six==1.16.0
|
||||
starlette==0.27.0
|
||||
anyio==3.7.1
|
||||
idna==3.4
|
||||
sniffio==1.3.0
|
||||
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
|
||||
91
start.sh
|
|
@ -1,22 +1,81 @@
|
|||
#!/bin/bash
|
||||
# Installiere Abhängigkeiten
|
||||
cd backend
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
|
||||
# Farben für die Ausgabe
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}Data Platform - Multi-Agent Service${NC}"
|
||||
echo -e "${BLUE}Startskript für Frontend und Backend${NC}"
|
||||
echo "----------------------------------------"
|
||||
|
||||
# Verzeichnisstruktur erstellen, falls sie nicht existiert
|
||||
mkdir -p backend/data
|
||||
mkdir -p backend/uploads
|
||||
mkdir -p backend/results
|
||||
mkdir -p backend/static
|
||||
|
||||
# Prüfen, ob Python installiert ist
|
||||
if command -v python3 &>/dev/null; then
|
||||
PYTHON_CMD="python3"
|
||||
elif command -v python &>/dev/null; then
|
||||
PYTHON_CMD="python"
|
||||
else
|
||||
echo "Python ist nicht installiert. Bitte installieren Sie Python 3.8 oder höher."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Virtuelle Umgebung erstellen, falls sie nicht existiert
|
||||
if [ ! -d "backend/venv" ]; then
|
||||
echo "Erstelle virtuelle Python-Umgebung..."
|
||||
cd backend
|
||||
$PYTHON_CMD -m venv venv
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# Virtuelle Umgebung aktivieren
|
||||
echo "Aktiviere virtuelle Umgebung..."
|
||||
source backend/venv/bin/activate 2>/dev/null || . backend/venv/bin/activate
|
||||
|
||||
# Abhängigkeiten installieren
|
||||
echo "Installiere Abhängigkeiten..."
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Migration durchführen
|
||||
python manage.py migrate
|
||||
python manage.py collectstatic --noinput
|
||||
# Backend als Hintergrundprozess starten
|
||||
echo "Starte Backend-Server..."
|
||||
cd backend
|
||||
uvicorn app:app --reload --host 0.0.0.0 --port 8000 &
|
||||
BACKEND_PID=$!
|
||||
cd ..
|
||||
|
||||
# React bauen
|
||||
cd ../frontend
|
||||
npm install
|
||||
npm run build
|
||||
# Kurz warten, um sicherzustellen, dass das Backend startet
|
||||
sleep 2
|
||||
|
||||
# React-Frontend nach Django-Statisches-Verzeichnis verschieben
|
||||
cp -r build/* ../backend/staticfiles/
|
||||
# Frontend-Server starten
|
||||
echo "Starte Frontend-Server..."
|
||||
cd frontend
|
||||
$PYTHON_CMD -m http.server 8080 &
|
||||
FRONTEND_PID=$!
|
||||
cd ..
|
||||
|
||||
# Starte Gunicorn (Django-Server)
|
||||
cd ../backend
|
||||
gunicorn backend.wsgi --bind 0.0.0.0:8000
|
||||
echo "----------------------------------------"
|
||||
echo -e "${GREEN}Server wurden gestartet!${NC}"
|
||||
echo "Frontend läuft auf: http://localhost:8080"
|
||||
echo "Backend API läuft auf: http://localhost:8000"
|
||||
echo "API-Dokumentation: http://localhost:8000/docs"
|
||||
echo -e "${BLUE}Drücke STRG+C, um beide Server zu beenden${NC}"
|
||||
|
||||
# Funktion zum Beenden der Server bei STRG+C
|
||||
cleanup() {
|
||||
echo -e "\n${GREEN}Beende Server...${NC}"
|
||||
kill $BACKEND_PID
|
||||
kill $FRONTEND_PID
|
||||
echo "Server wurden beendet"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Signal-Handler für STRG+C
|
||||
trap cleanup SIGINT
|
||||
|
||||
# Warten auf Benutzeraktion
|
||||
wait
|
||||
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": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||