mvp running

This commit is contained in:
valueon 2025-03-15 19:53:40 +01:00
parent e5d8e5a697
commit fce4c721c6
28 changed files with 2390 additions and 889 deletions

View file

@ -1,24 +1,125 @@
import asyncio
import uuid
import os
import json
import logging
import time
import configparser
import re
from typing import List, Dict, Any, Optional
from datetime import datetime
import logging
import json
import os
import sys
import httpx
from fastapi import HTTPException
import requests
from bs4 import BeautifulSoup
# Logger konfigurieren
logger = logging.getLogger(__name__)
# Konfiguration aus config.ini laden
def load_config():
config = configparser.ConfigParser()
config_path = os.path.join(os.path.dirname(__file__), 'config.ini')
# Standardkonfiguration
default_config = {
"openai": {
"api_key": "VOKEY",
"api_url": "https://api.openai.com/v1/chat/completions",
"model_name": "gpt-4o"
},
"application": {
"debug": "True",
"upload_dir": "./uploads",
"results_dir": "./results"
},
"webscraping": {
"timeout": "10",
"max_urls": "3",
"max_content_length": "3000",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
}
if not os.path.exists(config_path):
logger.warning(f"Konfigurationsdatei nicht gefunden: {config_path}")
return default_config
try:
config.read(config_path)
# Konfiguration aus der Datei lesen
result = {
"openai": {
"api_key": config.get('OpenAI', 'API_KEY', fallback=default_config["openai"]["api_key"]),
"api_url": config.get('OpenAI', 'API_URL', fallback=default_config["openai"]["api_url"]),
"model_name": config.get('OpenAI', 'MODEL_NAME', fallback=default_config["openai"]["model_name"])
},
"application": {
"debug": config.get('Application', 'DEBUG', fallback=default_config["application"]["debug"]),
"upload_dir": config.get('Application', 'UPLOAD_DIR', fallback=default_config["application"]["upload_dir"]),
"results_dir": config.get('Application', 'RESULTS_DIR', fallback=default_config["application"]["results_dir"])
},
"webscraping": {
"timeout": config.get('WebScraping', 'TIMEOUT', fallback=default_config["webscraping"]["timeout"]),
"max_urls": config.get('WebScraping', 'MAX_URLS', fallback=default_config["webscraping"]["max_urls"]),
"max_content_length": config.get('WebScraping', 'MAX_CONTENT_LENGTH', fallback=default_config["webscraping"]["max_content_length"]),
"user_agent": config.get('WebScraping', 'USER_AGENT', fallback=default_config["webscraping"]["user_agent"])
}
}
# Debug-Modus einstellen
if result["application"]["debug"].lower() == "true":
logging.basicConfig(level=logging.DEBUG)
logger.setLevel(logging.DEBUG)
logger.debug("Debug-Modus aktiviert")
return result
except Exception as e:
logger.error(f"Fehler beim Laden der Konfiguration: {e}")
return default_config
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.
Service für die Verwaltung und Ausführung von Multi-Agent-Workflows mit OpenAI GPT-4o.
"""
def __init__(self):
self.workflows = {}
self.results_dir = "./results"
# Konfiguration laden
self.config = load_config()
# Verzeichnisse aus der Konfiguration übernehmen
self.results_dir = self.config["application"]["results_dir"]
self.upload_dir = self.config["application"]["upload_dir"]
# Web-Scraping-Konfiguration
self.scraping_timeout = int(self.config["webscraping"]["timeout"])
self.scraping_max_urls = int(self.config["webscraping"]["max_urls"])
self.scraping_max_content_length = int(self.config["webscraping"]["max_content_length"])
self.scraping_user_agent = self.config["webscraping"]["user_agent"]
# Verzeichnisse erstellen
os.makedirs(self.results_dir, exist_ok=True)
os.makedirs(self.upload_dir, exist_ok=True)
logger.info(f"AgentService initialisiert mit:")
logger.info(f" - Modell: {self.config['openai']['model_name']}")
logger.info(f" - Ergebnisverzeichnis: {self.results_dir}")
logger.info(f" - Upload-Verzeichnis: {self.upload_dir}")
# Workflow-Speicher
self.workflows = {}
# HttpClient für API-Aufrufe
self.http_client = httpx.AsyncClient(
timeout=120.0, # Längeres Timeout für komplexe Anfragen
headers={
"Authorization": f"Bearer {self.config['openai']['api_key']}",
"Content-Type": "application/json"
}
)
async def execute_workflow(
self,
@ -29,10 +130,7 @@ class AgentService:
) -> 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.
Verwendet OpenAI GPT-4o für die Verarbeitung der Anfragen.
"""
logger.info(f"Starte Workflow {workflow_id} mit {len(agents)} Agenten und {len(files)} Dateien")
@ -52,10 +150,122 @@ class AgentService:
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
# Dateikontexte und Inhalte vorbereiten
file_contexts = []
file_contents = {}
# Verarbeitung pro Agent simulieren
for file in files:
file_id = file["id"]
file_name = file["name"]
file_type = file["type"]
file_path = file.get("path", "")
# Wenn kein Pfad angegeben ist, versuche, ihn aus dem Upload-Verzeichnis abzuleiten
if not file_path and file_name:
possible_path = os.path.join(self.upload_dir, file_name)
if os.path.exists(possible_path):
file_path = possible_path
logger.debug(f"Pfad für Datei {file_name} gefunden: {file_path}")
file_contexts.append({
"id": file_id,
"name": file_name,
"type": file_type,
"size": file.get("size", "Unbekannt"),
"path": file_path
})
# Dateiinhalt lesen, wenn der Pfad verfügbar ist
if file_path and os.path.exists(file_path):
try:
# Text-basierte Dateien direkt lesen
if file_type == "document":
# Einfache Textdateien
if file_name.endswith(('.txt', '.csv', '.md', '.json')):
with open(file_path, 'r', encoding='utf-8') as f:
file_contents[file_id] = f.read()
self._add_log(workflow_id, f"Datei {file_name} gelesen", "info")
# Excel-Dateien
elif file_name.endswith(('.xlsx', '.xls')):
import pandas as pd
try:
df = pd.read_excel(file_path)
file_contents[file_id] = f"Excel-Datei mit {len(df)} Zeilen und {len(df.columns)} Spalten.\n"
file_contents[file_id] += f"Spalten: {', '.join(df.columns.tolist())}\n"
file_contents[file_id] += "Erste 5 Zeilen:\n"
file_contents[file_id] += df.head(5).to_string()
self._add_log(workflow_id, f"Excel-Datei {file_name} gelesen", "info")
except Exception as e:
self._add_log(workflow_id, f"Fehler beim Lesen der Excel-Datei {file_name}: {str(e)}", "error")
# CSV-Dateien
elif file_name.endswith('.csv'):
import pandas as pd
try:
df = pd.read_csv(file_path)
file_contents[file_id] = f"CSV-Datei mit {len(df)} Zeilen und {len(df.columns)} Spalten.\n"
file_contents[file_id] += f"Spalten: {', '.join(df.columns.tolist())}\n"
file_contents[file_id] += "Erste 5 Zeilen:\n"
file_contents[file_id] += df.head(5).to_string()
self._add_log(workflow_id, f"CSV-Datei {file_name} gelesen", "info")
except Exception as e:
self._add_log(workflow_id, f"Fehler beim Lesen der CSV-Datei {file_name}: {str(e)}", "error")
# PDF-Dateien
elif file_name.endswith('.pdf'):
try:
# Falls PyPDF2 installiert ist
try:
from PyPDF2 import PdfReader
reader = PdfReader(file_path)
text = ""
for page in reader.pages:
text += page.extract_text() + "\n\n"
file_contents[file_id] = f"PDF mit {len(reader.pages)} Seiten.\nInhalt:\n{text[:2000]}..."
self._add_log(workflow_id, f"PDF-Datei {file_name} gelesen", "info")
except ImportError:
self._add_log(workflow_id, "PyPDF2 nicht installiert. PDF-Inhalt kann nicht extrahiert werden.", "warning")
file_contents[file_id] = f"PDF-Datei (Inhalt nicht verfügbar, PyPDF2 fehlt)"
except Exception as e:
self._add_log(workflow_id, f"Fehler beim Lesen der PDF-Datei {file_name}: {str(e)}", "error")
# Andere Dokumenttypen
else:
self._add_log(workflow_id, f"Nicht unterstütztes Dokumentformat: {file_name}", "warning")
file_contents[file_id] = f"Dateiinhalt nicht verfügbar (Nicht unterstütztes Format)"
# Bilddateien werden nicht direkt gelesen, nur Metadaten gespeichert
elif file_type == "image":
file_contents[file_id] = f"Bilddatei: {file_name} (Inhalt nicht als Text verfügbar)"
except Exception as e:
logger.error(f"Fehler beim Lesen der Datei {file_name}: {str(e)}")
self._add_log(workflow_id, f"Fehler beim Lesen der Datei {file_name}: {str(e)}", "error")
else:
if file_path:
self._add_log(workflow_id, f"Datei {file_name} nicht gefunden: {file_path}", "warning")
else:
self._add_log(workflow_id, f"Kein Pfad für Datei {file_name} verfügbar", "warning")
file_contents[file_id] = f"Dateiinhalt nicht verfügbar"
# Erstelle einen Kontext mit Dateiliste und Inhalten für leichteren Zugriff
file_context_text = "Verfügbare Dateien:\n" + "\n".join([f"- {file['name']} ({file['type']}, {file['size']})" for file in file_contexts])
# Füge Dateiinhalte hinzu (mit Längenbegrenzung)
for file_id, content in file_contents.items():
file_name = next((f['name'] for f in file_contexts if f['id'] == file_id), "Unbekannte Datei")
file_context_text += f"\n\n==== DATEIINHALT: {file_name} ====\n"
# Begrenze den Inhalt, um Token-Limits zu respektieren
max_content_length = 5000 # Anpassen je nach Anzahl der Dateien und Umfang
if len(content) > max_content_length:
file_context_text += content[:max_content_length] + "...\n[Dateiinhalt gekürzt aus Platzgründen]"
else:
file_context_text += content
self.workflows[workflow_id]["progress"] = 0.1
# Verarbeitung pro Agent ausführen
for i, agent in enumerate(agents):
agent_id = agent["id"]
agent_name = agent["name"]
@ -70,31 +280,85 @@ class AgentService:
agent_name
)
# Simuliere Verarbeitungszeit
# Fortschritt aktualisieren
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)
try:
# Agent-spezifische Anweisungen erstellen
agent_instructions = self._get_agent_instructions(agent_type)
# Vollständige Anfrage an OpenAI erstellen
full_prompt = f"""
# Aufgabe
{prompt}
# Dateikontexte
{file_context_text}
# Agent-Rolle
Du bist ein {agent_name} ({agent_type}).
{agent_instructions}
Bitte analysiere die Daten und beantworte die Anfrage gemäß deiner Rolle.
"""
# Für Web-Scraper: Führe Web-Scraping durch
if agent_type == "scraper":
self._add_log(workflow_id, "Führe Web-Scraping durch...", "info", agent_id, agent_name)
web_data = await self._scrape_web_data(workflow_id, prompt)
if web_data:
full_prompt += f"\n\n# Gescrapte Web-Daten\n{web_data}"
self._add_log(workflow_id, "Web-Scraping abgeschlossen", "info", agent_id, agent_name)
# API-Anfrage an OpenAI senden
response = await self._call_openai_api(full_prompt)
# Ergebnis extrahieren und speichern
result_content = response.get("choices", [{}])[0].get("message", {}).get("content", "Keine Antwort erhalten")
# Agent-Ergebnis erstellen
result = self._create_agent_result(workflow_id, agent, i, prompt, file_contexts, result_content)
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
)
except Exception as e:
logger.error(f"Fehler bei der Ausführung von Agent '{agent_name}': {str(e)}")
self._add_log(
workflow_id,
f"Fehler bei der Ausführung: {str(e)}",
"error",
agent_id,
agent_name
)
self.workflows[workflow_id]["agent_statuses"][agent_id] = "failed"
continue
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))
# Kurze Pause zwischen Agent-Aufrufen
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")
# Workflow-Status prüfen
failed_agents = [a for a, s in self.workflows[workflow_id]["agent_statuses"].items() if s == "failed"]
if failed_agents:
self._add_log(workflow_id, f"{len(failed_agents)} Agent(s) sind fehlgeschlagen", "error")
self._add_log(workflow_id, "Workflow mit Fehlern beendet", "error")
self.workflows[workflow_id]["status"] = "failed"
else:
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]["status"] = "completed"
self.workflows[workflow_id]["progress"] = 1.0
self.workflows[workflow_id]["completed_at"] = datetime.now().isoformat()
@ -103,6 +367,253 @@ class AgentService:
return workflow_id
async def _scrape_web_data(self, workflow_id: str, prompt: str) -> str:
"""
Führt Web-Scraping basierend auf dem Prompt durch
"""
try:
# Extrahiere mögliche Schlüsselwörter oder URLs aus dem Prompt
keywords = self._extract_keywords(prompt)
urls = self._extract_urls(prompt)
results = []
# Falls direkte URLs im Prompt enthalten sind
if urls:
self._add_log(workflow_id, f"Gefundene URLs: {', '.join(urls[:self.scraping_max_urls])}", "info")
for url in urls[:self.scraping_max_urls]: # Begrenze auf max_urls (Standard: 3)
try:
self._add_log(workflow_id, f"Scrape URL: {url}", "info")
content = self._scrape_url(url)
if content:
results.append(f"## Inhalt von {url}\n{content}")
self._add_log(workflow_id, f"Scraping von {url} erfolgreich", "info")
except Exception as e:
logger.error(f"Fehler beim Scrapen von {url}: {e}")
self._add_log(workflow_id, f"Fehler beim Scrapen von {url}: {e}", "error")
# Falls keine URLs, versuche Suche mit Schlüsselwörtern
elif keywords:
self._add_log(workflow_id, f"Verwende Keywords für Suche: {keywords}", "info")
search_results = self._search_web(keywords)
if search_results:
results.append(f"## Suchergebnisse für: {keywords}\n{search_results}")
self._add_log(workflow_id, "Suche abgeschlossen", "info")
if results:
return "\n\n".join(results)
self._add_log(workflow_id, "Keine relevanten Web-Daten gefunden", "warning")
return "Keine relevanten Web-Daten gefunden."
except Exception as e:
logger.error(f"Fehler beim Web-Scraping: {e}")
self._add_log(workflow_id, f"Fehler beim Web-Scraping: {e}", "error")
return f"Web-Scraping konnte nicht durchgeführt werden: {str(e)}"
def _extract_keywords(self, text: str) -> str:
"""Extrahiert Schlüsselwörter aus dem Text"""
# Einfache Implementierung - in der Praxis könntest du NLP verwenden
words = text.split()
# Filtere kurze Wörter und häufige Stopwörter
stopwords = ["einen", "einer", "eines", "keine", "nicht", "diese", "dieses", "zwischen",
"und", "oder", "aber", "denn", "wenn", "weil", "obwohl", "während", "für",
"mit", "von", "aus", "nach", "bei", "über", "unter", "durch", "gegen"]
keywords = [w for w in words if len(w) > 4 and w.lower() not in stopwords]
return " ".join(keywords[:5]) # Begrenze auf 5 Keywords
def _extract_urls(self, text: str) -> List[str]:
"""Extrahiert URLs aus dem Text"""
# Einfacher URL-Extraktions-Regex
url_pattern = re.compile(r'https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+[/\w\.-]*(?:\?\S+)?')
return url_pattern.findall(text)
def _scrape_url(self, url: str) -> str:
"""Scrapt den Inhalt einer URL"""
headers = {
'User-Agent': self.scraping_user_agent
}
response = requests.get(url, headers=headers, timeout=self.scraping_timeout)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
# Entferne Skripte, Styles und andere unwichtige Elemente
for script in soup(["script", "style", "meta", "noscript", "iframe"]):
script.extract()
# Extrahiere den Hauptinhalt
main_content = ""
# Versuche, Hauptcontainer zu finden (häufige IDs und Klassen)
main_elements = soup.select('main, #main, .main, #content, .content, article, .article, .post, #post')
if main_elements:
# Nehme den ersten gefundenen Hauptcontainer
main_content = main_elements[0].get_text(separator='\n', strip=True)
else:
# Falls kein Hauptcontainer gefunden, nehme den Body-Text
main_content = soup.body.get_text(separator='\n', strip=True)
# Bereinige den Text (entferne mehrfache Leerzeilen etc.)
lines = [line.strip() for line in main_content.split('\n') if line.strip()]
main_content = '\n'.join(lines)
# Begrenze die Länge
if len(main_content) > self.scraping_max_content_length:
main_content = main_content[:self.scraping_max_content_length] + "...\n[Inhalt gekürzt]"
return main_content
def _search_web(self, query: str) -> str:
"""
Simuliert eine Websuche (in einer vollständigen Implementierung
würdest du hier eine echte Suchmaschinen-API verwenden)
"""
# HINWEIS: Dies ist eine Simulation! In einer echten Anwendung
# würdest du Google Custom Search API, SerpAPI oder ähnliches verwenden
# Für eine echte Implementierung:
# - Google Custom Search API: https://developers.google.com/custom-search/v1/overview
# - SerpAPI: https://serpapi.com/
# - Oder ähnliche Dienste
return f"Hinweis: Dies ist eine Demo-Implementierung ohne echte Websuche. In der Produktion würde hier der Agent tatsächlich nach '{query}' suchen."
async def _call_openai_api(self, prompt: str) -> Dict[str, Any]:
"""Ruft die OpenAI API auf und gibt die Antwort zurück"""
try:
payload = {
"model": self.config["openai"]["model_name"],
"messages": [
{"role": "system", "content": "Du bist ein spezialisierter Agent in einem Multi-Agent-System zur Datenanalyse und -verarbeitung."},
{"role": "user", "content": prompt}
],
"temperature": 0.2, # Niedrige Temperatur für konsistentere Ergebnisse
"max_tokens": 2000
}
response = await self.http_client.post(
self.config["openai"]["api_url"],
json=payload
)
if response.status_code != 200:
logger.error(f"OpenAI API-Fehler: {response.status_code} - {response.text}")
raise HTTPException(status_code=500, detail="Fehler bei der Kommunikation mit OpenAI API")
return response.json()
except Exception as e:
logger.error(f"Fehler beim Aufruf der OpenAI API: {str(e)}")
raise HTTPException(status_code=500, detail=f"Fehler beim Aufruf der OpenAI API: {str(e)}")
def _get_agent_instructions(self, agent_type: str) -> str:
"""
Gibt agententypspezifische Anweisungen zurück, die aus der agents.json geladen werden.
Falls die Datei nicht existiert oder der Agententyp nicht gefunden wird,
werden Standard-Anweisungen zurückgegeben.
"""
try:
# Pfad zur agents.json-Datei
agents_file = os.path.join(os.path.dirname(__file__), 'data', 'agents.json')
# Überprüfen, ob die Datei existiert
if not os.path.exists(agents_file):
logger.warning(f"Agents-Definitionen nicht gefunden: {agents_file}")
return self._get_default_agent_instructions(agent_type)
# Datei lesen
with open(agents_file, 'r', encoding='utf-8') as f:
agents_data = json.load(f)
# Nach dem Agententyp suchen
for agent in agents_data:
if agent.get("type") == agent_type:
# Anweisungen zurückgeben, wenn vorhanden
instructions = agent.get("instructions")
if instructions:
logger.debug(f"Anweisungen für Agent-Typ '{agent_type}' aus agents.json geladen")
return instructions
# Wenn kein passender Agent gefunden wurde, Standardanweisungen verwenden
logger.warning(f"Keine Anweisungen für Agent-Typ '{agent_type}' in agents.json gefunden")
return self._get_default_agent_instructions(agent_type)
except Exception as e:
logger.error(f"Fehler beim Laden der Agent-Anweisungen aus agents.json: {e}")
return self._get_default_agent_instructions(agent_type)
def _get_default_agent_instructions(self, agent_type: str) -> str:
"""
Gibt Standard-Anweisungen für einen Agententyp zurück,
wenn keine spezifischen Anweisungen in der agents.json gefunden wurden.
"""
default_instructions = {
"analyzer": """
Als Datenanalyse-Agent ist es deine Aufgabe, die bereitgestellten Daten zu analysieren und wichtige Erkenntnisse zu extrahieren.
Folge diesen Anweisungen zur Analyse der Dateien:
1. Lese und verstehe den Inhalt der bereitgestellten Dateien gründlich
2. Identifiziere welchen Datentyp jede Datei enthält (z.B. Zeitreihendaten, kategorische Daten, Text)
3. Wenn es sich um tabellarische Daten handelt:
- Identifiziere Muster, Trends und Anomalien
- Berechne relevante statistische Kennzahlen (Mittelwerte, Mediane, Standardabweichungen)
- Suche nach Korrelationen zwischen verschiedenen Spalten
- Identifiziere Ausreißer und ungewöhnliche Datenpunkte
4. Wenn es sich um Textdaten handelt:
- Analysiere Schlüsselthemen und -begriffe
- Identifiziere Stimmung und Tonalität, wenn relevant
- Extrahiere zentrale Aussagen und Schlussfolgerungen
5. Erstelle eine strukturierte Zusammenfassung deiner Erkenntnisse
6. Gib konkrete, datengestützte Empfehlungen, wenn möglich
In deiner Antwort:
- Beginne mit einer kurzen Übersicht der analysierten Daten
- Struktur
- Strukturiere deine Erkenntnisse klar mit Überschriften und Aufzählungen
- Füge quantitative Erkenntnisse ein, wo immer möglich
- Schließe mit einer Zusammenfassung der wichtigsten Punkte ab
""",
"visualizer": """
Als Visualisierungs-Agent ist es deine Aufgabe, die Daten visuell zu beschreiben.
- Beschreibe, welche Art von Visualisierungen für die Daten sinnvoll wären
- Erkläre das Layout und die Komponenten der Visualisierung
- Beschreibe, wie die Daten grafisch dargestellt werden sollten
- Erkläre, welche Erkenntnisse aus dieser Visualisierung gewonnen werden können
""",
"writer": """
Als Text-Generator ist es deine Aufgabe, verständliche Berichte und Zusammenfassungen zu erstellen.
- Fasse die wichtigsten Erkenntnisse aus den Daten zusammen
- Strukturiere den Bericht klar und prägnant
- Verwende eine sachliche und professionelle Sprache
- Formuliere konkrete Empfehlungen basierend auf den Daten
- Nutze Markdown für bessere Formatierung
""",
"scraper": """
Als Web-Scraper-Agent ist es deine Aufgabe, Webseiten zu durchsuchen und relevante Informationen zu extrahieren.
Folge diesen Anweisungen:
1. Analysiere die bereitgestellten Web-Daten sorgfältig
2. Extrahiere die wichtigsten Informationen und Fakten aus den Webinhalten
3. Organisiere die Informationen in klare Kategorien
4. Identifiziere Trends, Muster und wichtige Erkenntnisse
5. Vergleiche die Informationen aus verschiedenen Quellen, wenn verfügbar
6. Fasse die gefundenen Informationen prägnant zusammen
7. Erkenne mögliche Einschränkungen oder Verzerrungen in den Quellen
In deiner Antwort:
- Beginne mit einer Zusammenfassung der gefundenen Informationen
- Strukturiere die extrahierten Daten in logische Abschnitte
- Hebe wichtige Fakten und Zahlen hervor
- Gib Quellenhinweise an
- Formuliere Schlussfolgerungen basierend auf den gesammelten Daten
"""
}
return default_instructions.get(agent_type, "Du bist ein Datenverarbeitungs-Agent. Analysiere die gegebenen Informationen und liefere relevante Erkenntnisse.")
def _add_log(
self,
workflow_id: str,
@ -126,76 +637,59 @@ class AgentService:
workflow["logs"].append(log_entry)
logger.info(f"Workflow {workflow_id}: {message}")
def _generate_simulated_result(
def _create_agent_result(
self,
workflow_id: str,
agent: Dict[str, Any],
index: int,
prompt: str,
files: List[Dict[str, Any]]
file_contexts: List[Dict[str, Any]],
content: str
) -> Dict[str, Any]:
"""Generiert ein simuliertes Ergebnis basierend auf dem Agententyp"""
"""Erstellt ein Ergebnisobjekt basierend auf dem Agententyp und der API-Antwort"""
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)}"
# Grundlegende Ergebnisstruktur
result = {
"id": f"result_{workflow_id}_{index}",
"agent_id": agent_id,
"agent_name": agent_name,
"timestamp": datetime.now().isoformat(),
"type": "text", # Standardtyp
"metadata": {
"files_processed": file_names,
"files_processed": [file["name"] for file in file_contexts],
"prompt": prompt
}
}
# Titel und Inhalt basierend auf dem Agententyp anpassen
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"
"content": content,
})
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]
}
]
}
}
"title": "Visualisierungsvorschlag",
"content": content,
"type": "chart" # Auch wenn kein echtes Diagramm, markieren wir es als solches
})
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."
"content": content,
})
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"
"title": "Web-Recherche Ergebnisse",
"content": content,
})
else:
result.update({
"title": "Verarbeitungsergebnis",
"type": "text",
"content": f"Allgemeines Ergebnis der Verarbeitung durch {agent_name}"
"title": f"Ergebnis von {agent_name}",
"content": content,
})
return result
@ -242,6 +736,10 @@ class AgentService:
return None
return workflow["results"]
async def close(self):
"""Schließt den HTTP-Client beim Beenden der Anwendung"""
await self.http_client.aclose()
# Singleton-Instanz des AgentService

View file

@ -1,6 +1,7 @@
import uvicorn
from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, Body, Query
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.responses import JSONResponse, Response
from fastapi.staticfiles import StaticFiles
from typing import List, Dict, Any, Optional
import uuid
@ -42,28 +43,35 @@ app.add_middleware(
allow_headers=["*"],
)
# Statische Dateien (für Frontend)
app.mount("/static", StaticFiles(directory="static"), name="static")
# Pfad zum Webparts-Verzeichnis
WEBPARTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "webparts")
# Verzeichnis für hochgeladene Dateien erstellen
UPLOAD_DIR = os.path.join(os.getcwd(), "uploads")
os.makedirs(UPLOAD_DIR, exist_ok=True)
# Komponenten des Frontends
app.mount("/webparts", StaticFiles(directory="webparts"), name="webparts")
@app.get("/", tags=["General"])
async def root():
"""API-Statusendpunkt"""
return {"status": "online", "message": "Data Platform API ist aktiv"}
# Test Element
@app.get("/api/test", tags=["Test"])
async def get_test():
return "OK 1.0"
# Workspace-Endpunkte
@app.get("/workspaces", tags=["Workspaces"], response_model=List[Workspace])
@app.get("/api/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)
@app.get("/api/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)
@ -72,7 +80,7 @@ async def get_workspace(workspace_id: str, db: Database = Depends(get_db)):
return workspace
@app.post("/workspaces", tags=["Workspaces"], response_model=Workspace)
@app.post("/api/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())
@ -89,8 +97,8 @@ async def create_workspace(workspace: Dict[str, Any] = Body(...), db: Database =
return new_workspace
# Agent-Endpunkte
@app.get("/agents", tags=["Agents"], response_model=List[Agent])
# Agenten-Endpunkte
@app.get("/api/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:
@ -98,7 +106,7 @@ async def get_agents(workspace_id: Optional[str] = Query(None), db: Database = D
return db.get_all_agents()
@app.post("/agents", tags=["Agents"], response_model=Agent)
@app.post("/api/agents", tags=["Agents"], response_model=Agent)
async def create_agent(
agent: Dict[str, Any] = Body(...),
db: Database = Depends(get_db)
@ -128,13 +136,13 @@ async def create_agent(
# Datei-Endpunkte
@app.get("/files", tags=["Files"], response_model=List[DataObject])
@app.get("/api/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"])
@app.post("/api/files/upload", tags=["Files"])
async def upload_file(
file: UploadFile = File(...),
db: Database = Depends(get_db)
@ -180,7 +188,7 @@ async def upload_file(
# Prompt-Endpunkte
@app.get("/prompts", tags=["Prompts"], response_model=List[Prompt])
@app.get("/api/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:
@ -188,7 +196,7 @@ async def get_prompts(workspace_id: Optional[str] = Query(None), db: Database =
return db.get_all_prompts()
@app.post("/prompts", tags=["Prompts"], response_model=Prompt)
@app.post("/api/prompts", tags=["Prompts"], response_model=Prompt)
async def create_prompt(
prompt: Dict[str, Any] = Body(...),
db: Database = Depends(get_db)
@ -216,7 +224,7 @@ async def create_prompt(
# Workflow-Endpunkte
@app.post("/workflow/run", tags=["Workflow"], response_model=WorkflowResponse)
@app.post("/api/workflow/run", tags=["Workflow"], response_model=WorkflowResponse)
async def run_workflow(
workflow_request: WorkflowRequest,
db: Database = Depends(get_db),
@ -264,7 +272,7 @@ async def run_workflow(
)
@app.get("/workflow/{workflow_id}/status", tags=["Workflow"])
@app.get("/api/workflow/{workflow_id}/status", tags=["Workflow"])
async def get_workflow_status(
workflow_id: str,
agent_service: AgentService = Depends(get_agent_service)
@ -276,7 +284,7 @@ async def get_workflow_status(
return status
@app.get("/workflow/{workflow_id}/logs", tags=["Workflow"], response_model=List[LogEntry])
@app.get("/api/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)
@ -288,7 +296,7 @@ async def get_workflow_logs(
return logs
@app.get("/workflow/{workflow_id}/results", tags=["Workflow"], response_model=List[Result])
@app.get("/api/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)
@ -301,5 +309,4 @@ async def get_workflow_results(
if __name__ == "__main__":
import uvicorn
uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)

15
backend/config.ini Normal file
View file

@ -0,0 +1,15 @@
[OpenAI]
API_KEY = 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
API_URL = https://api.openai.com/v1/chat/completions
MODEL_NAME = gpt-4o
[Application]
DEBUG = True
UPLOAD_DIR = ./uploads
RESULTS_DIR = ./results
[WebScraping]
TIMEOUT = 10
MAX_URLS = 3
MAX_CONTENT_LENGTH = 3000
USER_AGENT = Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36

View file

@ -1,4 +0,0 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

47
backend/data/agents.json Normal file
View file

@ -0,0 +1,47 @@
[
{
"id": "agent_001",
"name": "Datenanalyse-Agent",
"type": "analyzer",
"workspace_id": "workspace_001",
"capabilities": ["Datenanalyse", "Statistik", "Trendanalyse"],
"description": "Spezialisiert auf die Analyse von strukturierten Daten und Informationen.",
"instructions": "Als Datenanalyse-Agent ist es deine Aufgabe, die bereitgestellten Daten zu analysieren und wichtige Erkenntnisse zu extrahieren.\n\nFolge diesen Anweisungen zur Analyse der Dateien:\n1. Lese und verstehe den Inhalt der bereitgestellten Dateien gründlich\n2. Identifiziere welchen Datentyp jede Datei enthält (z.B. Zeitreihendaten, kategorische Daten, Text)\n3. Wenn es sich um tabellarische Daten handelt:\n - Identifiziere Muster, Trends und Anomalien\n - Berechne relevante statistische Kennzahlen (Mittelwerte, Mediane, Standardabweichungen)\n - Suche nach Korrelationen zwischen verschiedenen Spalten\n - Identifiziere Ausreißer und ungewöhnliche Datenpunkte\n4. Wenn es sich um Textdaten handelt:\n - Analysiere Schlüsselthemen und -begriffe\n - Identifiziere Stimmung und Tonalität, wenn relevant\n - Extrahiere zentrale Aussagen und Schlussfolgerungen\n5. Erstelle eine strukturierte Zusammenfassung deiner Erkenntnisse\n6. Gib konkrete, datengestützte Empfehlungen, wenn möglich\n\nIn deiner Antwort:\n- Beginne mit einer kurzen Übersicht der analysierten Daten\n- Strukturiere deine Erkenntnisse klar mit Überschriften und Aufzählungen\n- Füge quantitative Erkenntnisse ein, wo immer möglich\n- Schließe mit einer Zusammenfassung der wichtigsten Punkte ab"
},
{
"id": "agent_002",
"name": "Visualisierungs-Agent",
"type": "visualizer",
"workspace_id": "workspace_001",
"capabilities": ["Datenvisualisierung", "Diagrammerstellung"],
"description": "Erstellt visuelle Darstellungen aus Daten",
"instructions": "Als Visualisierungs-Agent ist es deine Aufgabe, die Daten visuell zu beschreiben.\n\nFolge diesen Anweisungen:\n1. Analysiere die bereitgestellten Daten und identifiziere die wichtigsten Aspekte für eine Visualisierung\n2. Empfehle geeignete Visualisierungstypen basierend auf der Art der Daten:\n - Für zeitliche Verläufe: Liniendiagramme, Area-Charts\n - Für Kategorienvergleiche: Balkendiagramme, gestapelte Balken\n - Für Verteilungen: Histogramme, Box-Plots\n - Für Anteile: Kreisdiagramme, Donut-Charts\n - Für Beziehungen: Streudiagramme, Heatmaps\n3. Beschreibe detailliert, wie die Visualisierung aufgebaut werden sollte:\n - Welche Daten auf welcher Achse abgebildet werden sollten\n - Farben und ihre Bedeutung\n - Beschriftungen und Legenden\n - Skalen und deren Anpassung\n4. Erkläre, welche Erkenntnisse die Visualisierung vermitteln würde\n5. Gib Empfehlungen für Interaktionsmöglichkeiten oder zusätzliche Visualisierungen"
},
{
"id": "agent_003",
"name": "Text-Generator",
"type": "writer",
"workspace_id": "workspace_001",
"capabilities": ["Texterstellung", "Zusammenfassung"],
"description": "Verfasst verständliche Berichte und Zusammenfassungen",
"instructions": "Als Text-Generator ist es deine Aufgabe, verständliche Berichte und Zusammenfassungen zu erstellen.\n\nFolge diesen Anweisungen:\n1. Destilliere die wichtigsten Erkenntnisse aus den bereitgestellten Daten und Analysen\n2. Strukturiere den Bericht logisch mit:\n - Einer prägnanten Einleitung mit Kontext und Haupterkenntnissen\n - Einem detaillierten Hauptteil, der die Erkenntnisse systematisch präsentiert\n - Einem Abschluss mit Zusammenfassung und Handlungsempfehlungen\n3. Verwende eine klare, präzise und faktenbasierte Sprache\n4. Integriere Zahlen und Daten direkt in den Text, um Aussagen zu untermauern\n5. Nutze Markdown für eine bessere Formatierung:\n - Überschriften für verschiedene Abschnitte\n - Aufzählungspunkte für Listen\n - Hervorhebungen für wichtige Punkte\n - Tabellen für strukturierte Daten"
},
{
"id": "agent_004",
"name": "Web-Scraper",
"type": "scraper",
"workspace_id": "workspace_002",
"capabilities": ["Datensammlung", "Webanalyse", "Echtzeit-Recherche"],
"description": "Sammelt und analysiert Daten aus Webquellen in Echtzeit",
"instructions": "Als Web-Scraper-Agent ist es deine Aufgabe, Webseiten zu durchsuchen und relevante Informationen zu extrahieren.\n\nFolge diesen Anweisungen:\n1. Analysiere die bereitgestellten Web-Daten sorgfältig\n2. Extrahiere die wichtigsten Informationen und Fakten aus den Webinhalten\n3. Organisiere die Informationen in klare Kategorien\n4. Identifiziere Trends, Muster und wichtige Erkenntnisse\n5. Vergleiche die Informationen aus verschiedenen Quellen, wenn verfügbar\n6. Fasse die gefundenen Informationen prägnant zusammen\n7. Erkenne mögliche Einschränkungen oder Verzerrungen in den Quellen\n\nIn deiner Antwort:\n- Beginne mit einer Zusammenfassung der gefundenen Informationen\n- Strukturiere die extrahierten Daten in logische Abschnitte\n- Hebe wichtige Fakten und Zahlen hervor\n- Gib Quellenhinweise an\n- Formuliere Schlussfolgerungen basierend auf den gesammelten Daten"
},
{
"id": "agent_005",
"name": "Marktanalyse-Agent",
"type": "analyzer",
"workspace_id": "workspace_002",
"capabilities": ["Wettbewerbsanalyse", "Markttrends"],
"description": "Spezialisiert auf Wettbewerbsanalyse und Markttrends",
"instructions": "Als Marktanalyse-Agent ist es deine Aufgabe, Wettbewerbsdaten und Markttrends zu analysieren.\n\nFolge diesen Anweisungen:\n1. Identifiziere die wichtigsten Wettbewerber und ihre Positionierung im Markt\n2. Analysiere Markttrends und -entwicklungen:\n - Wachstumsraten und Marktgrößen\n - Veränderungen im Konsumentenverhalten\n - Technologische Entwicklungen\n - Regulatorische Änderungen\n3. Vergleiche Produkte und Dienstleistungen nach relevanten Kriterien\n4. Identifiziere Stärken, Schwächen, Chancen und Risiken (SWOT-Analyse)\n5. Erstelle eine fundierte Einschätzung der Marktposition\n6. Entwickle strategische Empfehlungen basierend auf der Marktanalyse"
}
]

65
backend/data/files.json Normal file
View file

@ -0,0 +1,65 @@
[
{
"id": "file_001",
"name": "Quartalsbericht Q1 2025.pdf",
"type": "document",
"content_type": "application/pdf",
"size": 2500000,
"upload_date": "2025-03-14T00:25:25.076526",
"path": "uploads/dummy_file1.pdf"
},
{
"id": "file_002",
"name": "Marktanalyse-Diagramm.png",
"type": "image",
"content_type": "image/png",
"size": 1150000,
"upload_date": "2025-03-14T00:25:25.076526",
"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,
"upload_date": "2025-03-14T00:25:25.076526",
"path": "uploads/dummy_file3.xlsx"
},
{
"id": "77bb2097-7044-4267-aae6-f2ca1c119ab8",
"name": "11 PowerOn HLD - Journey.pdf",
"type": "document",
"path": "D:\\Athi\\Local\\Web\\git-apps\\gateway\\backend\\uploads\\77bb2097-7044-4267-aae6-f2ca1c119ab8.pdf",
"content_type": "application/pdf",
"size": 56687,
"upload_date": "2025-03-14T00:25:55.641089"
},
{
"id": "f066b3d6-7d59-4851-a8eb-de4334f4a64e",
"name": "11 PowerOn HLD - Journey.pdf",
"type": "document",
"path": "D:\\Athi\\Local\\Web\\git-apps\\gateway\\backend\\uploads\\f066b3d6-7d59-4851-a8eb-de4334f4a64e.pdf",
"content_type": "application/pdf",
"size": 56687,
"upload_date": "2025-03-14T09:46:37.306858"
},
{
"id": "bb36ca5e-cae0-441d-b59c-feb80ecf6d51",
"name": "11 PowerOn HLD - Journey.pdf",
"type": "document",
"path": "D:\\Athi\\Local\\Web\\git-apps\\gateway\\backend\\uploads\\bb36ca5e-cae0-441d-b59c-feb80ecf6d51.pdf",
"content_type": "application/pdf",
"size": 56687,
"upload_date": "2025-03-14T10:53:08.043663"
},
{
"id": "a40ec285-afab-45fe-9668-ba99d4da3a70",
"name": "ReportingSheet2025.xlsx",
"type": "document",
"path": "D:\\Athi\\Local\\Web\\git-apps\\gateway\\backend\\uploads\\a40ec285-afab-45fe-9668-ba99d4da3a70.xlsx",
"content_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"size": 14727,
"upload_date": "2025-03-14T13:55:15.194910"
}
]

14
backend/data/prompts.json Normal file
View file

@ -0,0 +1,14 @@
[
{
"id": "prompt_001",
"content": "Analysiere die Quartalsdaten und erstelle eine Zusammenfassung mit wichtigsten Trends",
"workspace_id": "workspace_001",
"created_at": "2025-03-14T00:25:25.076526"
},
{
"id": "prompt_002",
"content": "Erstelle eine Prognose für das nächste Quartal basierend auf historischen Daten",
"workspace_id": "workspace_001",
"created_at": "2025-03-14T00:25:25.076526"
}
]

View file

@ -0,0 +1,18 @@
[
{
"id": "workspace_001",
"name": "Datenanalyse-Projekt",
"created_at": "2025-03-14T00:25:25.076526",
"prompts": [],
"agents": [],
"dataObjectReferences": []
},
{
"id": "workspace_002",
"name": "Marktforschung",
"created_at": "2025-03-14T00:25:25.076526",
"prompts": [],
"agents": [],
"dataObjectReferences": []
}
]

View file

@ -141,7 +141,7 @@ class Database:
"created_at": datetime.now().isoformat()
}
])
def _load_data(self, file_path):
"""Lädt Daten aus einer JSON-Datei"""
try:

View file

@ -7,8 +7,10 @@ class Agent(BaseModel):
id: str
name: str
type: str
workspace_id: str
capabilities: List[str] = []
description: Optional[str] = None
instructions: Optional[str] = None
class DataObject(BaseModel):
"""Datenmodell für ein Datenobjekt"""
@ -33,7 +35,7 @@ class Workspace(BaseModel):
created_at: str
prompts: List[Prompt] = []
agents: List[Agent] = []
dataObjectReferences: List[str] = []
dataObjectReferences: List[str] = []
class WorkflowRequest(BaseModel):
"""Anforderung zur Ausführung eines Workflows"""

View file

@ -1,4 +0,0 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

View file

@ -0,0 +1,15 @@
<!-- Meine Agents Ansicht -->
<div id="agents-view">
<h2>Meine Agents</h2>
<div class="card">
<div class="section-header">
<h3>Verfügbare Agents</h3>
<button class="add-btn" id="add-agent-btn">
<i class="fas fa-plus"></i> Neuen Agent hinzufügen
</button>
</div>
<div id="agents-list-container">
<!-- Wird dynamisch gefüllt -->
</div>
</div>
</div>

View file

@ -0,0 +1,204 @@
(function() {
// DOM-Elemente
let addAgentBtn, agentsListContainer;
// Initialisierungsfunktion für das Agenten-Modul
function initAgentsModule(globalState) {
// DOM-Elemente referenzieren
addAgentBtn = document.getElementById('add-agent-btn');
agentsListContainer = document.getElementById('agents-list-container');
// Event-Listener hinzufügen
setupEventListeners(globalState);
// Initialdaten rendern
renderMyAgents(globalState);
}
// Event-Listener einrichten
function setupEventListeners(globalState) {
// Neuen Agenten hinzufügen
addAgentBtn.addEventListener('click', () => {
openAgentCreationModal(globalState);
});
}
// Modal zum Erstellen eines neuen Agenten öffnen
function openAgentCreationModal(globalState) {
// Einfaches Prompt als Platzhalter - in einer realen Anwendung würde dies
// durch ein ansprechendes Modal oder Formular ersetzt
const agentName = prompt('Name des neuen Agenten:');
const agentType = prompt('Typ des Agenten (z.B. Analyse, Transformation):');
const agentDescription = prompt('Beschreibung des Agenten:');
if (agentName && agentType) {
const newAgent = {
id: `agent_${Date.now()}`,
name: agentName,
type: agentType,
description: agentDescription || '',
workspace_id: globalState.currentWorkspace?.id,
capabilities: []
};
// Agent zur globalen Liste hinzufügen
globalState.availableAgents.push(newAgent);
// UI aktualisieren
renderMyAgents(globalState);
// Optional: Agent speichern
saveAgent(newAgent, globalState);
}
}
// Meine Agenten rendern
function renderMyAgents(globalState) {
console.info()
if (!agentsListContainer) {
console.error("No agents container")
return;
}
agentsListContainer.innerHTML = '';
// Agenten für den aktuellen Workspace filtern
const workspaceAgents = globalState.availableAgents.filter(
agent => agent.workspace_id === globalState.currentWorkspace.id
);
// console.info("debug: agentid-wsid: "+ (globalState.availableAgents[0].workspace_id) + " - " + globalState.currentWorkspace.id + ", wsAgents: " + workspaceAgents.length)
if (workspaceAgents.length === 0) {
agentsListContainer.innerHTML = '<div class="empty-state">Keine Agenten vorhanden.</div>';
return;
}
workspaceAgents.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>
<div class="agent-actions">
<button class="edit-agent-btn" data-id="${agent.id}">
<i class="fas fa-edit"></i> Bearbeiten
</button>
<button class="delete-agent-btn" data-id="${agent.id}">
<i class="fas fa-trash"></i> Löschen
</button>
</div>
`;
agentsListContainer.appendChild(agentItem);
});
// Event-Listener für Agenten-Aktionen einrichten
setupAgentActionListeners(globalState);
}
// Event-Listener für Agenten-Aktionen
function setupAgentActionListeners(globalState) {
// Bearbeiten-Button
document.querySelectorAll('.edit-agent-btn').forEach(btn => {
btn.addEventListener('click', () => {
const agentId = btn.getAttribute('data-id');
const agent = globalState.availableAgents.find(a => a.id === agentId);
if (agent) {
editAgent(agent, globalState);
}
});
});
// Löschen-Button
document.querySelectorAll('.delete-agent-btn').forEach(btn => {
btn.addEventListener('click', () => {
const agentId = btn.getAttribute('data-id');
if (confirm('Möchten Sie diesen Agenten wirklich löschen?')) {
deleteAgent(agentId, globalState);
}
});
});
}
// Agent bearbeiten
function editAgent(agent, globalState) {
// Einfaches Prompt als Platzhalter - in einer realen Anwendung würde dies
// durch ein ansprechendes Modal oder Formular ersetzt
const newName = prompt('Name des Agenten:', agent.name);
const newType = prompt('Typ des Agenten:', agent.type);
const newDescription = prompt('Beschreibung des Agenten:', agent.description);
if (newName) {
// Agent aktualisieren
agent.name = newName;
agent.type = newType || agent.type;
agent.description = newDescription || agent.description;
// UI aktualisieren
renderMyAgents(globalState);
// Optional: Aktualisierung auf dem Server
updateAgent(agent);
}
}
// Agent speichern
async function saveAgent(agentData, globalState) {
try {
// Agent auf dem Server speichern
const response = await window.globalUtils.postData('/api/agents/save', agentData);
// Rückmeldung vom Server verarbeiten
if (response && response.id) {
// Wenn der Server eine ID zurückgibt, diese aktualisieren
const index = globalState.availableAgents.findIndex(a => a.id === agentData.id);
if (index !== -1) {
globalState.availableAgents[index].id = response.id;
}
// UI aktualisieren
renderMyAgents(globalState);
}
} catch (error) {
console.error("Fehler beim Speichern des Agenten:", error);
}
}
// Agent aktualisieren
async function updateAgent(agent) {
try {
// Agent auf dem Server aktualisieren
await window.globalUtils.postData('/api/agents/update', agent);
} catch (error) {
console.error("Fehler beim Aktualisieren des Agenten:", error);
}
}
// Agent löschen
async function deleteAgent(agentId, globalState) {
try {
// Aus dem globalen State entfernen
globalState.availableAgents = globalState.availableAgents.filter(a => a.id !== agentId);
// UI aktualisieren
renderMyAgents(globalState);
// Agent vom Server löschen
await window.globalUtils.postData('/api/agents/delete', { id: agentId });
} catch (error) {
console.error("Fehler beim Löschen des Agenten:", error);
}
}
// Modul-Initialisierung exportieren
window.initAgentsModule = initAgentsModule;
})();

View file

@ -0,0 +1,18 @@
<!-- Meine Daten Ansicht -->
<div id="data-view">
<h2>Meine Daten</h2>
<div class="card">
<div class="section-header">
<h3>Verfügbare Dateien</h3>
<button class="upload-btn" id="data-upload-btn">
<i class="fas fa-upload"></i> Neue Dateien hochladen
</button>
<input type="file" id="data-file-input" multiple hidden>
</div>
<div class="files-container">
<ul class="file-list" id="my-files-list">
<!-- Wird dynamisch gefüllt -->
</ul>
</div>
</div>
</div>

View file

@ -0,0 +1,111 @@
// data-module.js
(function() {
// DOM-Elemente
let dataFileInput, uploadBtn, myFilesList;
// Initialisierungsfunktion für das Daten-Modul
function initDataModule(globalState) {
// DOM-Elemente referenzieren
dataFileInput = document.getElementById('data-file-input');
uploadBtn = document.getElementById('data-upload-btn');
myFilesList = document.getElementById('my-files-list');
// Event-Listener hinzufügen
setupEventListeners(globalState);
// Initialdaten rendern
renderMyFiles(globalState);
}
// Event-Listener einrichten
function setupEventListeners(globalState) {
// Datei-Upload
uploadBtn.addEventListener('click', () => dataFileInput.click());
dataFileInput.addEventListener('change',
window.globalUtils.handleFileUpload(dataFileInput, (newFile) => {
// Nach erfolgreichem Upload
globalState.availableFiles.push(newFile);
renderMyFiles(globalState);
})
);
}
// Meine Dateien rendern
function renderMyFiles(globalState) {
if (!myFilesList) return;
myFilesList.innerHTML = '';
if (globalState.availableFiles.length === 0) {
myFilesList.innerHTML = '<div class="empty-state">Keine Dateien vorhanden.</div>';
return;
}
globalState.availableFiles.forEach(file => {
const li = document.createElement('li');
li.className = 'file-item';
li.innerHTML = `
<div class="file-item-name">
<i class="fas ${file.type === 'document' ? 'fa-file-alt' : 'fa-file-image'}"></i>
<span>${file.name}</span>
</div>
<div class="file-item-info">
<span>${file.size || ''}</span>
<span>${new Date(file.upload_date || Date.now()).toLocaleString()}</span>
</div>
<div class="file-actions">
<button class="view-file-btn" data-id="${file.id}">
<i class="fas fa-eye"></i>
</button>
<button class="delete-file-btn" data-id="${file.id}">
<i class="fas fa-trash"></i>
</button>
</div>
`;
myFilesList.appendChild(li);
});
// Event-Listener für Datei-Aktionen
setupFileActionListeners(globalState);
}
// Event-Listener für Datei-Aktionen
function setupFileActionListeners(globalState) {
// Datei anzeigen
document.querySelectorAll('.view-file-btn').forEach(btn => {
btn.addEventListener('click', () => {
const fileId = btn.getAttribute('data-id');
const file = globalState.availableFiles.find(f => f.id === fileId);
if (file && file.path) {
window.open(file.path, '_blank');
} else {
alert('Datei nicht verfügbar');
}
});
});
// Datei löschen
document.querySelectorAll('.delete-file-btn').forEach(btn => {
btn.addEventListener('click', async () => {
const fileId = btn.getAttribute('data-id');
if (confirm('Möchten Sie diese Datei wirklich löschen?')) {
try {
// TODO: API-Endpunkt zum Löschen implementieren
// await fetch(`/files/${fileId}`, { method: 'DELETE' });
// Aus dem globalen State entfernen
globalState.availableFiles = globalState.availableFiles.filter(f => f.id !== fileId);
// UI aktualisieren
renderMyFiles(globalState);
} catch (error) {
console.error("Fehler beim Löschen der Datei:", error);
}
}
});
});
}
// Modul-Initialisierung exportieren
window.initDataModule = initDataModule;
})();

View file

@ -0,0 +1,15 @@
<!-- Meine Prompts Ansicht -->
<div id="prompts-view">
<h2>Meine Prompts</h2>
<div class="card">
<div class="section-header">
<h3>Gespeicherte Prompts</h3>
<button class="add-btn" id="add-prompt-btn">
<i class="fas fa-plus"></i> Neuen Prompt erstellen
</button>
</div>
<div id="prompts-list">
<!-- Wird dynamisch gefüllt -->
</div>
</div>
</div>

View file

@ -0,0 +1,168 @@
// prompts-module.js
(function() {
// DOM-Elemente
let addPromptBtn, promptsList;
// Initialisierungsfunktion für das Prompts-Modul
function initPromptsModule(globalState) {
// DOM-Elemente referenzieren
addPromptBtn = document.getElementById('add-prompt-btn');
promptsList = document.getElementById('prompts-list');
// Event-Listener hinzufügen
setupEventListeners(globalState);
// Initialdaten rendern
renderMyPrompts(globalState);
}
// Event-Listener einrichten
function setupEventListeners(globalState) {
// Neuen Prompt hinzufügen
addPromptBtn.addEventListener('click', () => {
const newPrompt = prompt('Geben Sie den neuen Prompt ein:');
if (newPrompt && newPrompt.trim()) {
const promptData = {
id: `prompt_${Date.now()}`,
content: newPrompt.trim(),
created_at: new Date().toISOString()
};
// Prompt zur globalen Liste hinzufügen
globalState.availablePrompts.push(promptData);
// UI aktualisieren
renderMyPrompts(globalState);
// Optional: Prompt speichern
savePrompt(promptData, globalState);
}
});
}
// Meine Prompts rendern
function renderMyPrompts(globalState) {
if (!promptsList) return;
promptsList.innerHTML = '';
if (globalState.availablePrompts.length === 0) {
promptsList.innerHTML = '<div class="empty-state">Keine Prompts vorhanden.</div>';
return;
}
globalState.availablePrompts.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>
<button class="delete-prompt-btn" data-id="${prompt.id}">
<i class="fas fa-trash"></i>
</button>
</div>
`;
promptsList.appendChild(promptItem);
});
// Event-Listener für Prompt-Aktionen einrichten
setupPromptActionListeners(globalState);
}
// Event-Listener für Prompt-Aktionen
function setupPromptActionListeners(globalState) {
// "Verwenden"-Button
document.querySelectorAll('.use-prompt-btn').forEach(btn => {
btn.addEventListener('click', () => {
const promptId = btn.getAttribute('data-id');
const selectedPrompt = globalState.availablePrompts.find(p => p.id === promptId);
if (selectedPrompt) {
// Workflow-Modul aktivieren und Prompt übernehmen
document.querySelector('a[href="#workflow"]').click();
// Prompt in Workflow-Eingabefeld setzen
const promptInput = document.getElementById('prompt-input');
const promptSelect = document.getElementById('prompt-select');
if (promptInput) {
promptInput.value = selectedPrompt.content;
}
if (promptSelect) {
// Wenn der Prompt bereits existiert, ihn in der Dropdown auswählen
const existingOption = Array.from(promptSelect.options).find(
option => option.textContent.includes(selectedPrompt.content.substring(0, 50))
);
if (existingOption) {
promptSelect.value = existingOption.value;
}
}
}
});
});
// "Löschen"-Button
document.querySelectorAll('.delete-prompt-btn').forEach(btn => {
btn.addEventListener('click', async () => {
const promptId = btn.getAttribute('data-id');
if (confirm('Möchten Sie diesen Prompt wirklich löschen?')) {
try {
// Aus dem globalen State entfernen
globalState.availablePrompts = globalState.availablePrompts.filter(p => p.id !== promptId);
// UI aktualisieren
renderMyPrompts(globalState);
// Optional: Prompt vom Server löschen
await deletePrompt(promptId);
} catch (error) {
console.error("Fehler beim Löschen des Prompts:", error);
}
}
});
});
}
// Prompt speichern
async function savePrompt(promptData, globalState) {
try {
// Prompt auf dem Server speichern
const response = await window.globalUtils.postData('/api/prompts/save', promptData);
// Rückmeldung vom Server verarbeiten
if (response && response.id) {
// Wenn der Server eine ID zurückgibt, diese aktualisieren
const index = globalState.availablePrompts.findIndex(p => p.id === promptData.id);
if (index !== -1) {
globalState.availablePrompts[index].id = response.id;
}
// UI aktualisieren
renderMyPrompts(globalState);
}
} catch (error) {
console.error("Fehler beim Speichern des Prompts:", error);
}
}
// Prompt löschen
async function deletePrompt(promptId) {
try {
// Prompt vom Server löschen
await window.globalUtils.postData('/api/prompts/delete', { id: promptId });
} catch (error) {
console.error("Fehler beim Löschen des Prompts:", error);
throw error;
}
}
// Modul-Initialisierung exportieren
window.initPromptsModule = initPromptsModule;
})();

View file

@ -0,0 +1,103 @@
<!-- Workflow Module -->
<div class="workflow-container">
<!-- 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>
<!-- Prompt eingeben oder auswählen -->
<div class="card">
<h3>2. Prompt eingeben oder auswählen</h3>
<!-- Prompt-Auswahl -->
<div class="form-group">
<label for="prompt-select" class="prompt-select-label">
Gespeicherte Prompts:
</label>
<select id="prompt-select" class="prompt-select">
<option value="">-- Prompt auswählen --</option>
<!-- Wird dynamisch gefüllt -->
</select>
</div>
<!-- Eingabefeld für eigenen Prompt -->
<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>

View file

@ -0,0 +1,408 @@
// workflow-module.js
(function() {
// Workflow-Modul-Zustand
let workflowState = {
selectedFiles: [],
selectedAgents: [],
prompt: "",
logs: [],
results: [],
isRunning: false
};
// DOM-Elemente
let availableFiles, selectedFiles, emptyFilesState,
fileInput, uploadBtn, promptInput, promptSelect,
agentList, resetBtn, startWorkflowBtn,
executionLog, resultsContainer,
emptyResultsState, resultsList;
// Initialisierungsfunktion für das Workflow-Modul
function initWorkflowModule(globalState) {
// DOM-Elemente referenzieren
availableFiles = document.getElementById('available-files');
selectedFiles = document.getElementById('selected-files');
emptyFilesState = document.getElementById('empty-files-state');
fileInput = document.getElementById('file-input');
uploadBtn = document.getElementById('upload-btn');
promptInput = document.getElementById('prompt-input');
promptSelect = document.getElementById('prompt-select');
agentList = document.getElementById('agent-list');
resetBtn = document.getElementById('reset-btn');
startWorkflowBtn = document.getElementById('start-workflow-btn');
executionLog = document.getElementById('execution-log');
resultsContainer = document.getElementById('results-container');
emptyResultsState = document.getElementById('empty-results-state');
resultsList = document.getElementById('results-list');
// Event-Listener hinzufügen
setupEventListeners(globalState);
// Initialdaten rendern
renderFiles(globalState);
renderPromptSelector(globalState);
renderAgents(globalState);
}
// Event-Listener einrichten
function setupEventListeners(globalState) {
// Datei-Upload
uploadBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change',
window.globalUtils.handleFileUpload(fileInput, (newFile) => {
// Nach erfolgreichem Upload
workflowState.selectedFiles.push(newFile);
renderSelectedFiles();
renderFiles(globalState);
})
);
// Prompt-Eingabe
promptInput.addEventListener('input', (e) => {
workflowState.prompt = e.target.value;
});
// Prompt-Auswahl
promptSelect.addEventListener('change', () => {
const selectedPromptId = promptSelect.value;
if (selectedPromptId) {
const selectedPrompt = globalState.availablePrompts.find(p => p.id === selectedPromptId);
if (selectedPrompt) {
promptInput.value = selectedPrompt.content;
workflowState.prompt = selectedPrompt.content;
}
}
});
// Zurücksetzen
resetBtn.addEventListener('click', resetWorkflow);
// Workflow starten
startWorkflowBtn.addEventListener('click', () => runWorkflow(globalState));
}
// Verfügbare Dateien rendern
function renderFiles(globalState) {
availableFiles.innerHTML = '';
globalState.availableFiles.forEach(file => {
const li = document.createElement('li');
li.className = 'file-item';
const isSelected = workflowState.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) {
workflowState.selectedFiles.push(file);
} else {
workflowState.selectedFiles = workflowState.selectedFiles.filter(f => f.id !== file.id);
}
renderSelectedFiles();
});
availableFiles.appendChild(li);
});
}
// Ausgewählte Dateien rendern
function renderSelectedFiles() {
if (workflowState.selectedFiles.length === 0) {
emptyFilesState.style.display = 'flex';
selectedFiles.innerHTML = '';
} else {
emptyFilesState.style.display = 'none';
selectedFiles.innerHTML = '';
workflowState.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');
workflowState.selectedFiles = workflowState.selectedFiles.filter(f => f.id !== fileId);
renderSelectedFiles();
renderFiles(window.globalState);
});
});
}
}
// Prompt-Auswahlbox rendern
function renderPromptSelector(globalState) {
// Existierende Optionen entfernen (außer der ersten)
while (promptSelect.options.length > 1) {
promptSelect.remove(1);
}
// Prompts aus dem globalen State hinzufügen
globalState.availablePrompts.forEach(prompt => {
const option = document.createElement('option');
option.value = prompt.id;
option.textContent = prompt.content.substring(0, 50) + (prompt.content.length > 50 ? '...' : '');
promptSelect.appendChild(option);
});
}
// Agenten rendern
function renderAgents(globalState) {
agentList.innerHTML = '';
// Agenten für den aktuellen Workspace filtern
const workspaceAgents = globalState.availableAgents.filter(
agent => agent.workspace_id === globalState.currentWorkspace?.id
);
if (workspaceAgents.length === 0) {
agentList.innerHTML = '<div class="empty-state">Keine Agenten im ausgewählten Workspace verfügbar.</div>';
return;
}
workspaceAgents.forEach(agent => {
const li = document.createElement('li');
li.className = 'agent-item';
const isSelected = workflowState.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) {
workflowState.selectedAgents.push(agent);
} else {
workflowState.selectedAgents = workflowState.selectedAgents.filter(a => a.id !== agent.id);
}
});
agentList.appendChild(li);
});
}
// Ausführungsprotokoll aktualisieren
function addLogEntry(message, type = 'info') {
const log = {
id: `log_${Date.now()}`,
message,
type,
timestamp: new Date().toISOString()
};
workflowState.logs.push(log);
renderLogs();
return log;
}
// Protokoll rendern
function renderLogs() {
if (workflowState.logs.length === 0) {
executionLog.innerHTML = '<div class="log-empty-state">Workflow noch nicht gestartet. Protokoll wird hier angezeigt.</div>';
return;
}
executionLog.innerHTML = '';
workflowState.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;
}
// Ergebnis hinzufügen
function addResult(result) {
workflowState.results.push(result);
renderResults();
}
// Ergebnisse rendern
function renderResults() {
if (workflowState.results.length === 0) {
emptyResultsState.style.display = 'flex';
resultsList.style.display = 'none';
return;
}
emptyResultsState.style.display = 'none';
resultsList.style.display = 'block';
resultsList.innerHTML = '';
workflowState.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(globalState) {
if (workflowState.selectedFiles.length === 0 ||
workflowState.selectedAgents.length === 0 ||
!workflowState.prompt.trim()) {
alert("Bitte wählen Sie Dateien und Agenten aus und geben Sie einen Prompt ein.");
return;
}
workflowState.isRunning = true;
workflowState.logs = [];
workflowState.results = [];
startWorkflowBtn.textContent = 'Wird ausgeführt...';
startWorkflowBtn.classList.add('running');
startWorkflowBtn.disabled = true;
renderLogs();
renderResults();
try {
// API-Aufruf an das Backend für Workflow-Ausführung
const workflowData = {
workspace_id: globalState.currentWorkspace.id,
files: workflowState.selectedFiles.map(file => file.id),
agents: workflowState.selectedAgents.map(agent => agent.id),
prompt: workflowState.prompt,
workflow_name: "Workflow " + new Date().toLocaleString()
};
const response = await window.globalUtils.postData('/api/workflow/run', workflowData);
if (response && response.workflow_id) {
const workflowId = response.workflow_id;
addLogEntry("Workflow gestartet", "info");
addLogEntry(`Workflow-ID: ${workflowId}`, "info");
// Polling für Workflow-Status und -Ergebnisse starten
pollWorkflowStatus(workflowId);
} else {
throw new Error("Workflow konnte nicht gestartet werden");
}
} catch (error) {
console.error("Fehler beim Starten des Workflows:", error);
workflowState.isRunning = false;
startWorkflowBtn.textContent = 'Workflow starten';
startWorkflowBtn.classList.remove('running');
startWorkflowBtn.disabled = false;
addLogEntry(`Fehler beim Starten des Workflows: ${error.message}`, "error");
}
}
// Workflow-Status abfragen
async function pollWorkflowStatus(workflowId) {
try {
// Status abrufen
const status = await window.globalUtils.fetchData(`/api/workflow/${workflowId}/status`);
// Logs abrufen und anzeigen
const logs = await window.globalUtils.fetchData(`/api/workflow/${workflowId}/logs`);
workflowState.logs = logs;
renderLogs();
// Status aktualisieren
if (status) {
// Fortschritt anzeigen
addLogEntry(`Fortschritt: ${Math.round(status.progress * 100)}%`, "info");
// Wenn der Workflow abgeschlossen ist, Ergebnisse abrufen
if (status.status === 'completed' || status.status === 'failed') {
const results = await window.globalUtils.fetchData(`/api/workflow/${workflowId}/results`);
workflowState.results = results;
renderResults();
addLogEntry(`Workflow ${status.status === 'completed' ? 'erfolgreich beendet' : 'fehlgeschlagen'}`,
status.status === 'completed' ? "success" : "error");
workflowState.isRunning = false;
startWorkflowBtn.textContent = 'Workflow starten';
startWorkflowBtn.classList.remove('running');
startWorkflowBtn.disabled = false;
return;
}
}
// Weiteres Polling nach einer kurzen Verzögerung
setTimeout(() => pollWorkflowStatus(workflowId), 2000);
} catch (error) {
console.error("Fehler beim Abrufen des Workflow-Status:", error);
addLogEntry(`Fehler beim Abrufen des Workflow-Status: ${error.message}`, "error");
workflowState.isRunning = false;
startWorkflowBtn.textContent = 'Workflow starten';
startWorkflowBtn.classList.remove('running');
startWorkflowBtn.disabled = false;
}
}
// Workflow zurücksetzen
function resetWorkflow() {
workflowState.selectedFiles = [];
workflowState.selectedAgents = [];
promptInput.value = "";
promptSelect.selectedIndex = 0;
workflowState.prompt = "";
renderFiles(window.globalState);
renderSelectedFiles();
renderAgents(window.globalState);
}
// Modul-Initialisierung exportieren
window.initWorkflowModule = initWorkflowModule;
})();

BIN
backend/webparts/test.js Normal file

Binary file not shown.

15
backend/webparts/test.txt Normal file
View file

@ -0,0 +1,15 @@
<!-- Meine Agents Ansicht -->
<div id="agents-view" style="display:none;">
<h2>Meine Agents</h2>
<div class="card">
<div class="section-header">
<h3>Verfügbare Agents</h3>
<button class="add-btn" id="add-agent-btn">
<i class="fas fa-plus"></i> Neuen Agent hinzufügen
</button>
</div>
<div id="agents-list-container">
<!-- Wird dynamisch gefüllt -->
</div>
</div>
</div>

View file

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Data Platform - Multi-Agent Service</title>
<title>PowerOn | 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>
@ -13,7 +13,7 @@
<div class="navbar-container">
<h1 class="navbar-logo">Data Platform</h1>
<div class="navbar-user">
<span>Demo User</span>
<span>Shelly Miller</span>
<button class="icon-button">
<i class="fas fa-cog"></i>
</button>
@ -61,144 +61,15 @@
<!-- 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>
<!-- Module werden dynamisch geladen -->
<div id="part-workflow"></div>
<div id="part-data"></div>
<div id="part-prompts"></div>
<div id="part-agents"></div>
</main>
</div>
<script src="script.js"></script>
<!-- Module scripts -->
<script src="main.js"></script>
</body>
</html>

321
frontend/main.js Normal file
View file

@ -0,0 +1,321 @@
document.addEventListener('DOMContentLoaded',async function(){
// Globale Hilfsfunktionen und Initialisierung
// State-Management
const globalState = {
workspaces: [],
currentWorkspace: null,
availableFiles: [],
availableAgents: [],
availablePrompts: [],
};
async function loadModuleHtml(filename) {
// Backend-URL für HTML Module verwenden
try {
const apiBaseUrl = `${window.location.protocol}//${window.location.hostname}:8000`;
const modulePath = `${apiBaseUrl}/webparts/${filename}`;
console.log(`Lade HTML-Modul von: ${modulePath}`);
const response = await fetch(modulePath);
if (!response.ok) {
throw new Error(`HTTP-Fehler beim Laden des Moduls: ${response.status} ${response.statusText}`);
}
const htmlContent = await response.text();
return htmlContent;
} catch (error) {
console.error(`Fehler beim Laden des Moduls ${filename}:`, error);
throw error;
}
}
async function loadModuleJs(filename) {
try {
const apiBaseUrl = `${window.location.protocol}//${window.location.hostname}:8000`;
const response = await fetch(`${apiBaseUrl}/webparts/${filename}`);
if (!response.ok) {
throw new Error(`Failed to load JS module: ${response.status} ${response.statusText}`);
}
const jsContent = await response.text();
// Create a script element and add it to the page
const scriptElement = document.createElement('script');
scriptElement.textContent = jsContent;
document.head.appendChild(scriptElement);
console.log(`JS module ${filename} loaded successfully`);
} catch (error) {
console.error(`Error loading JS module ${filename}:`, error);
}
}
function fetchFromApi(url) {
// Basis-URL für die API mit korrektem Port
const apiBaseUrl = `${window.location.protocol}//${window.location.hostname}:8000`;
const fullUrl = url.startsWith('http') ? url : `${apiBaseUrl}${url}`;
console.log(`Fetching data from: ${fullUrl}`);
return fullUrl
}
// Hilfsfunktion zum Abrufen von Daten vom Backend
async function fetchData(url) {
try {
fullUrl=fetchFromApi(url)
const response = await fetch(fullUrl);
if (!response.ok) {
throw new Error(`Format-Fehler beim Abrufen von ${fullUrl}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error(`Genereller Fehler beim Abrufen von ${fullUrl}:`, error);
return [];
}
}
// Hilfsfunktion zum Senden von Daten an das Backend
async function postData(url, data) {
try {
fullUrl=fetchFromApi(url)
const response = await fetch(fullUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`Fehler beim Senden an ${fullUrl}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error(`Fehler beim Senden an ${fullUrl}:`, error);
throw error;
}
}
// Globale Initialisierungsfunktion
async function initialize() {
try {
// Load Module Scripts
loadModuleJs("part-workflow.js")
loadModuleJs("part-data.js")
loadModuleJs("part-prompts.js")
loadModuleJs("part-agents.js")
// Daten vom Backend abrufen
globalState.workspaces = await fetchData('/api/workspaces');
globalState.availableFiles = await fetchData('/api/files');
globalState.availablePrompts = await fetchData('/api/prompts');
// Wenn Workspaces vorhanden sind, den ersten auswählen
if (globalState.workspaces.length > 0) {
globalState.currentWorkspace = globalState.workspaces[0];
// Agenten für den aktuellen Workspace abrufen
globalState.availableAgents = await fetchData(`/api/agents?workspace_id=${globalState.currentWorkspace.id}`);
console.info("Debug agents set for: "+`/api/agents?workspace_id=${globalState.currentWorkspace.id}`+", result="+globalState.availableAgents)
}
// Event-Listener für Menüpunkte
setupMenuNavigation();
// Workspaces rendern, nachdem alle Daten geladen wurden
renderWorkspaces();
} catch (error) {
console.error("Fehler beim Initialisieren der Daten:", error);
}
}
// Menünavigation einrichten
function setupMenuNavigation() {
const menuItems = {
'workflow': document.querySelector('a[href="#workflow"]').parentElement,
'data': document.querySelector('a[href="#data"]').parentElement,
'prompts': document.querySelector('a[href="#prompts"]').parentElement,
'agents': document.querySelector('a[href="#agents"]').parentElement
};
const moduleContainers = {
'workflow': document.getElementById('part-workflow'),
'data': document.getElementById('part-data'),
'prompts': document.getElementById('part-prompts'),
'agents': document.getElementById('part-agents')
};
// Menüpunkte initialisieren
Object.keys(menuItems).forEach(key => {
menuItems[key].addEventListener('click', function(e) {
e.preventDefault();
// Aktive Klasse von allen Menüpunkten entfernen
Object.values(menuItems).forEach(item => item.classList.remove('active'));
// Alle Module ausblenden
Object.values(moduleContainers).forEach(container => container.style.display = 'none')
// Aktuelle Ansicht aktivieren
this.classList.add('active');
moduleContainers[key].style.display = 'block';
// Laden und Aktualisieren der Moduldaten
switch(key) {
case 'workflow':
loadWorkflowModule();
break;
case 'data':
loadDataModule();
break;
case 'prompts':
loadPromptsModule();
break;
case 'agents':
loadAgentsModule();
break;
}
});
});
// Initial das Workflow-Modul laden
loadWorkflowModule();
}
// Modul-Ladungsfunktionen
async function loadWorkflowModule() {
try {
// Dynamisches Laden des Workflow-Moduls
const moduleContent = await loadModuleHtml('part-workflow.html');
const workflowModule = document.getElementById('part-workflow');
workflowModule.innerHTML = moduleContent;
// Initialisiere Workflow-Modul-Funktionalität
if (window.initWorkflowModule) {
window.initWorkflowModule(globalState);
}
} catch (error) {
console.error("Fehler beim Laden des Workflow-Moduls:", error);
}
}
async function loadDataModule() {
try {
// Dynamisches Laden des Daten-Moduls
const moduleContent = await loadModuleHtml('part-data.html');
const dataModule = document.getElementById('part-data');
dataModule.innerHTML = moduleContent;
// Initialisiere Daten-Modul-Funktionalität
if (window.initDataModule) {
window.initDataModule(globalState);
}
} catch (error) {
console.error("Fehler beim Laden des Daten-Moduls:", error);
}
}
async function loadPromptsModule() {
try {
// Dynamisches Laden des Prompts-Moduls
const moduleContent = await loadModuleHtml('part-prompts.html');
const promptsModule = document.getElementById('part-prompts');
promptsModule.innerHTML = moduleContent;
// Initialisiere Prompts-Modul-Funktionalität
if (window.initPromptsModule) {
window.initPromptsModule(globalState);
}
} catch (error) {
console.error("Fehler beim Laden des Prompts-Moduls:", error);
}
}
async function loadAgentsModule() {
try {
// Dynamisches Laden des Agenten-Moduls
const moduleContent = await loadModuleHtml('part-agents.html');
const agentsModule = document.getElementById('part-agents');
agentsModule.innerHTML = moduleContent;
// Initialisiere Agenten-Modul-Funktionalität
if (window.initAgentsModule) {
window.initAgentsModule(globalState);
}
} catch (error) {
console.error("Fehler beim Laden des Agenten-Moduls:", error);
}
}
// Workspace-Funktionen
function renderWorkspaces() {
const workspaceList = document.getElementById('workspace-list');
workspaceList.innerHTML = '';
globalState.workspaces.forEach(workspace => {
const li = document.createElement('li');
li.className = `workspace-item ${workspace.id === globalState.currentWorkspace?.id ? 'active' : ''}`;
li.innerHTML = `
<i class="fas fa-folder"></i>
<span>${workspace.name}</span>
`;
li.addEventListener('click', async () => {
globalState.currentWorkspace = workspace;
// Agenten für den neuen Workspace abrufen
globalState.availableAgents = await fetchData(`/api/agents?workspace_id=${workspace.id}`);
renderWorkspaces();
// Aktualisiere aktive Module
loadWorkflowModule();
loadAgentsModule();
});
workspaceList.appendChild(li);
});
}
// Gemeinsame Utility-Funktionen
function handleFileUpload(fileInput, onUploadSuccess) {
return async () => {
if (fileInput.files.length > 0) {
for (const file of fileInput.files) {
try {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/files/upload', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`Fehler beim Hochladen der Datei: ${response.statusText}`);
}
const newFile = await response.json();
if (onUploadSuccess) {
onUploadSuccess(newFile);
}
// Globale Dateiliste aktualisieren
globalState.availableFiles.push(newFile);
} catch (error) {
console.error("Fehler beim Hochladen der Datei:", error);
}
}
// Eingabefeld zurücksetzen
fileInput.value = "";
}
};
}
// Initialisierung starten
initialize();
// Globale Funktionen für Module exportieren
window.globalUtils = {
fetchData,
postData,
handleFileUpload,
renderWorkspaces
};
}
);

View file

@ -1,654 +0,0 @@
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();
});

View file

@ -552,4 +552,245 @@ h4 {
padding-bottom: 0.5rem;
margin-bottom: 0.75rem;
font-weight: 500;
}
/* Styles für die Prompt-Auswahl */
.form-group {
margin-bottom: 1rem;
}
.prompt-select-label {
display: block;
font-size: 0.875rem;
font-weight: 500;
color: #4b5563;
margin-bottom: 0.5rem;
}
.prompt-select {
width: 100%;
padding: 0.5rem;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
background-color: white;
font-size: 0.875rem;
margin-bottom: 1rem;
}
/* Styles für die Prompt-Liste */
.prompt-item {
background-color: white;
border: 1px solid #e5e7eb;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1rem;
}
.prompt-content {
font-size: 0.875rem;
white-space: pre-line;
margin-bottom: 0.5rem;
padding: 0.75rem;
background-color: #f9fafb;
border-radius: 0.25rem;
border: 1px solid #e5e7eb;
}
.prompt-meta {
font-size: 0.75rem;
color: #6b7280;
margin-bottom: 0.5rem;
}
.prompt-actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
.use-prompt-btn {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.5rem;
background-color: #10b981;
color: white;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 500;
}
.use-prompt-btn:hover {
background-color: #059669;
}
.delete-prompt-btn {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.5rem;
background-color: #ef4444;
color: white;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 500;
}
.delete-prompt-btn:hover {
background-color: #dc2626;
}
/* Styles für die Agenten-Liste */
.agent-list-item {
background-color: white;
border: 1px solid #e5e7eb;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1rem;
}
.agent-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
}
.agent-header h4 {
margin: 0;
font-size: 1rem;
font-weight: 600;
}
.agent-type {
font-size: 0.75rem;
background-color: #e0f2fe;
color: #0369a1;
padding: 0.125rem 0.375rem;
border-radius: 9999px;
}
.agent-description {
font-size: 0.875rem;
margin-bottom: 0.75rem;
color: #4b5563;
}
.agent-capabilities {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
margin-bottom: 0.75rem;
}
.capability-tag {
font-size: 0.75rem;
background-color: #f3f4f6;
color: #4b5563;
padding: 0.125rem 0.375rem;
border-radius: 9999px;
}
.agent-actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
.edit-agent-btn {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.5rem;
background-color: #3b82f6;
color: white;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 500;
}
.edit-agent-btn:hover {
background-color: #2563eb;
}
.delete-agent-btn {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.5rem;
background-color: #ef4444;
color: white;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 500;
}
.delete-agent-btn:hover {
background-color: #dc2626;
}
/* Styles für die Dateien-Liste */
.file-actions {
display: flex;
gap: 0.5rem;
}
.view-file-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.75rem;
height: 1.75rem;
background-color: #3b82f6;
color: white;
border-radius: 0.25rem;
font-size: 0.875rem;
}
.view-file-btn:hover {
background-color: #2563eb;
}
.delete-file-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.75rem;
height: 1.75rem;
background-color: #ef4444;
color: white;
border-radius: 0.25rem;
font-size: 0.875rem;
}
.delete-file-btn:hover {
background-color: #dc2626;
}
/* Styles für Abschnittsüberschriften */
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.section-header h3 {
margin: 0;
}
.add-btn {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.375rem 0.75rem;
background-color: #10b981;
color: white;
border-radius: 0.375rem;
font-size: 0.875rem;
font-weight: 500;
}
.add-btn:hover {
background-color: #059669;
}

View file

@ -8,4 +8,11 @@ six==1.16.0
starlette==0.27.0
anyio==3.7.1
idna==3.4
sniffio==1.3.0
sniffio==1.3.0
httpx==0.24.1
fastapi==0.103.1
pandas==2.0.3
openpyxl==3.1.2
PyPDF2==3.0.1
beautifulsoup4==4.12.2
requests==2.31.0

View file

@ -7,7 +7,7 @@ echo ----------------------------------------
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
if not exist backend\webparts mkdir backend\webparts
:: Prüfen, ob Python installiert ist
python --version >nul 2>&1

View file

@ -13,7 +13,7 @@ echo "----------------------------------------"
mkdir -p backend/data
mkdir -p backend/uploads
mkdir -p backend/results
mkdir -p backend/static
mkdir -p backend/webparts
# Prüfen, ob Python installiert ist
if command -v python3 &>/dev/null; then