mvp running
This commit is contained in:
parent
e5d8e5a697
commit
fce4c721c6
28 changed files with 2390 additions and 889 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
15
backend/config.ini
Normal 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
|
||||
4
backend/data/.gitignore
vendored
4
backend/data/.gitignore
vendored
|
|
@ -1,4 +0,0 @@
|
|||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
||||
47
backend/data/agents.json
Normal file
47
backend/data/agents.json
Normal 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
65
backend/data/files.json
Normal 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
14
backend/data/prompts.json
Normal 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"
|
||||
}
|
||||
]
|
||||
18
backend/data/workspaces.json
Normal file
18
backend/data/workspaces.json
Normal 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": []
|
||||
}
|
||||
]
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
4
backend/static/.gitignore
vendored
4
backend/static/.gitignore
vendored
|
|
@ -1,4 +0,0 @@
|
|||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
||||
15
backend/webparts/part-agents.html
Normal file
15
backend/webparts/part-agents.html
Normal 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>
|
||||
204
backend/webparts/part-agents.js
Normal file
204
backend/webparts/part-agents.js
Normal 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;
|
||||
})();
|
||||
18
backend/webparts/part-data.html
Normal file
18
backend/webparts/part-data.html
Normal 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>
|
||||
111
backend/webparts/part-data.js
Normal file
111
backend/webparts/part-data.js
Normal 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;
|
||||
})();
|
||||
15
backend/webparts/part-prompts.html
Normal file
15
backend/webparts/part-prompts.html
Normal 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>
|
||||
168
backend/webparts/part-prompts.js
Normal file
168
backend/webparts/part-prompts.js
Normal 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;
|
||||
})();
|
||||
103
backend/webparts/part-workflow.html
Normal file
103
backend/webparts/part-workflow.html
Normal 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>
|
||||
408
backend/webparts/part-workflow.js
Normal file
408
backend/webparts/part-workflow.js
Normal 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
BIN
backend/webparts/test.js
Normal file
Binary file not shown.
15
backend/webparts/test.txt
Normal file
15
backend/webparts/test.txt
Normal 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>
|
||||
|
|
@ -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
321
frontend/main.js
Normal 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
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
2
start.sh
2
start.sh
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue