feat: Dynamische KI-Modelle, verbessertes Memory-System und Chat-Überarbeitung
🎯 KI-Modellverwaltung - Dynamisches Laden verfügbarer Modelle via opencode models - 29 Modelle verfügbar (opencode, anthropic, ollama) - Gruppierung nach Anbieter in UI - Cache-Mechanismus (1h TTL) für Performance - API-Endpoint /api/models für Modellabfrage 🧠 Memory-System komplett überarbeitet - JSON-basierte strukturierte Erinnerungen statt Markdown-Chaos - Separate Memory-Typen: tasks.json, notes.json, research.json - Automatische Memory-Zusammenfassung im Systemprompt - Limitierung auf letzte 100 Einträge pro Typ - Vollständige Task-Ergebnisse statt abgeschnittener Texte 📁 Agenten-Ordnerstruktur - work/ Verzeichnis für Agent-Dateien - memory/ Verzeichnis für strukturierte Erinnerungen - Agenten arbeiten nur in eigenem work-Verzeichnis - Absolute Pfade werden übergeben - Dateien-UI zeigt Agent-Work-Folders 💬 Chat-System überarbeitet - Echte Agent-Ausführung statt Mock-Responses - Server-Sent Events für Live-Streaming - Session-basierte Chat-History - Loading-Spinner und Status-Anzeigen - Automatisches Speichern in Session 🎭 Personality Integration - personality.md wird jetzt geladen - Persönlichkeit vor Systemprompt eingefügt - Gilt für alle: Chat, Tasks, Orchestrator, Email-Poller ✨ Weitere Verbesserungen - Alle Agenten nutzen execute_agent_task() zentral - Memory-Speicherung nach jedem Task - Work-Files in Datei-Verwaltung sichtbar - System-Dateien ausgeblendet - API-Route für Agent-Work-Dateien
This commit is contained in:
parent
84b2fe3dd7
commit
93eb8c6d47
83 changed files with 1692 additions and 1517 deletions
550
app.py
550
app.py
|
|
@ -20,6 +20,189 @@ load_dotenv()
|
|||
|
||||
# ── Agent Konfiguration ───────────────────────────────────────────────────────
|
||||
AGENT_CONFIG_FILE = os.path.join(os.path.dirname(__file__), 'agent_config.json')
|
||||
AGENTS_BASE_DIR = os.path.join(os.path.dirname(__file__), 'agents')
|
||||
|
||||
# Cache für verfügbare Modelle
|
||||
_available_models_cache = None
|
||||
_models_cache_time = None
|
||||
MODELS_CACHE_TTL = 3600 # Cache für 1 Stunde
|
||||
|
||||
# ── Agent Memory System ────────────────────────────────────────────────────────
|
||||
def ensure_agent_structure(agent_key):
|
||||
"""Stellt sicher, dass die Ordnerstruktur für einen Agenten existiert."""
|
||||
agent_dir = os.path.join(AGENTS_BASE_DIR, agent_key)
|
||||
work_dir = os.path.join(agent_dir, 'work')
|
||||
memory_dir = os.path.join(agent_dir, 'memory')
|
||||
|
||||
os.makedirs(agent_dir, exist_ok=True)
|
||||
os.makedirs(work_dir, exist_ok=True)
|
||||
os.makedirs(memory_dir, exist_ok=True)
|
||||
|
||||
return {
|
||||
'agent_dir': agent_dir,
|
||||
'work_dir': work_dir,
|
||||
'memory_dir': memory_dir
|
||||
}
|
||||
|
||||
def get_agent_memory(agent_key, memory_type='tasks'):
|
||||
"""Lädt Erinnerungen eines Agenten aus JSON-Datei.
|
||||
|
||||
memory_type kann sein:
|
||||
- 'tasks': Erledigte Tasks
|
||||
- 'notes': Notizen
|
||||
- 'conversations': Konversationen
|
||||
- 'research': Research-Ergebnisse
|
||||
"""
|
||||
dirs = ensure_agent_structure(agent_key)
|
||||
memory_file = os.path.join(dirs['memory_dir'], f'{memory_type}.json')
|
||||
|
||||
if os.path.exists(memory_file):
|
||||
try:
|
||||
with open(memory_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
pass
|
||||
return []
|
||||
|
||||
def add_agent_memory(agent_key, memory_type, entry):
|
||||
"""Fügt eine Erinnerung hinzu.
|
||||
|
||||
entry sollte ein dict sein mit mindestens:
|
||||
- timestamp: ISO-Format
|
||||
- title: Kurze Beschreibung
|
||||
- content: Detaillierter Inhalt
|
||||
- metadata: Zusätzliche Infos (optional)
|
||||
"""
|
||||
dirs = ensure_agent_structure(agent_key)
|
||||
memory_file = os.path.join(dirs['memory_dir'], f'{memory_type}.json')
|
||||
|
||||
memories = get_agent_memory(agent_key, memory_type)
|
||||
|
||||
# Timestamp hinzufügen wenn nicht vorhanden
|
||||
if 'timestamp' not in entry:
|
||||
entry['timestamp'] = datetime.now().isoformat()
|
||||
|
||||
# ID hinzufügen
|
||||
entry['id'] = len(memories) + 1
|
||||
|
||||
memories.append(entry)
|
||||
|
||||
# Nur die letzten 100 Einträge behalten
|
||||
memories = memories[-100:]
|
||||
|
||||
with open(memory_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(memories, f, indent=2, ensure_ascii=False)
|
||||
|
||||
return entry
|
||||
|
||||
def get_agent_work_files(agent_key):
|
||||
"""Gibt alle Dateien im work-Ordner eines Agenten zurück."""
|
||||
dirs = ensure_agent_structure(agent_key)
|
||||
work_dir = dirs['work_dir']
|
||||
|
||||
files = []
|
||||
if os.path.exists(work_dir):
|
||||
for filename in os.listdir(work_dir):
|
||||
filepath = os.path.join(work_dir, filename)
|
||||
if os.path.isfile(filepath):
|
||||
stat = os.stat(filepath)
|
||||
files.append({
|
||||
'name': filename,
|
||||
'size': stat.st_size,
|
||||
'modified': datetime.fromtimestamp(stat.st_mtime).isoformat(),
|
||||
'path': filepath
|
||||
})
|
||||
|
||||
return sorted(files, key=lambda x: x['modified'], reverse=True)
|
||||
|
||||
def get_agent_memory_summary(agent_key):
|
||||
"""Generiert eine Zusammenfassung aller Erinnerungen für den Systemprompt."""
|
||||
tasks = get_agent_memory(agent_key, 'tasks')
|
||||
notes = get_agent_memory(agent_key, 'notes')
|
||||
|
||||
summary = []
|
||||
|
||||
if tasks:
|
||||
summary.append("## Letzte Tasks (letzte 5)")
|
||||
for task in tasks[-5:]:
|
||||
summary.append(f"- [{task.get('timestamp', 'N/A')}] {task.get('title', 'Unbekannt')}")
|
||||
if task.get('result'):
|
||||
summary.append(f" Ergebnis: {task.get('result', '')[:200]}")
|
||||
|
||||
if notes:
|
||||
summary.append("\n## Wichtige Notizen")
|
||||
for note in notes[-5:]:
|
||||
summary.append(f"- {note.get('content', '')[:100]}")
|
||||
|
||||
return '\n'.join(summary) if summary else "Keine Erinnerungen vorhanden."
|
||||
|
||||
def get_available_models(force_refresh=False):
|
||||
"""Lädt die verfügbaren KI-Modelle dynamisch von opencode."""
|
||||
global _available_models_cache, _models_cache_time
|
||||
|
||||
# Cache prüfen
|
||||
if not force_refresh and _available_models_cache is not None and _models_cache_time is not None:
|
||||
if (time.time() - _models_cache_time) < MODELS_CACHE_TTL:
|
||||
return _available_models_cache
|
||||
|
||||
try:
|
||||
# opencode models ausführen
|
||||
result = subprocess.run(
|
||||
['opencode', 'models'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
models = []
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
line = line.strip()
|
||||
if line:
|
||||
models.append(line)
|
||||
|
||||
# Nach Anbieter gruppieren
|
||||
grouped = {}
|
||||
for model in models:
|
||||
if '/' in model:
|
||||
provider, name = model.split('/', 1)
|
||||
if provider not in grouped:
|
||||
grouped[provider] = []
|
||||
grouped[provider].append(model)
|
||||
|
||||
_available_models_cache = {
|
||||
'models': models,
|
||||
'grouped': grouped,
|
||||
'count': len(models)
|
||||
}
|
||||
_models_cache_time = time.time()
|
||||
return _available_models_cache
|
||||
except Exception as e:
|
||||
logging.warning(f"[ModelLoader] Fehler beim Laden der Modelle: {e}")
|
||||
|
||||
# Fallback auf hardcodierte Modelle wenn Laden fehlschlägt
|
||||
fallback = {
|
||||
'models': [
|
||||
'opencode/big-pickle',
|
||||
'opencode/gpt-5-nano',
|
||||
'opencode/glm-5-free',
|
||||
'opencode/minimax-m2.5-free',
|
||||
'opencode/trinity-large-preview-free'
|
||||
],
|
||||
'grouped': {
|
||||
'opencode': [
|
||||
'opencode/big-pickle',
|
||||
'opencode/gpt-5-nano',
|
||||
'opencode/glm-5-free',
|
||||
'opencode/minimax-m2.5-free',
|
||||
'opencode/trinity-large-preview-free'
|
||||
]
|
||||
},
|
||||
'count': 5
|
||||
}
|
||||
_available_models_cache = fallback
|
||||
_models_cache_time = time.time()
|
||||
return fallback
|
||||
|
||||
def get_agent_config():
|
||||
"""Lädt die Agentenkonfiguration aus der JSON-Datei."""
|
||||
|
|
@ -173,12 +356,27 @@ task_queue_lock = threading.Lock()
|
|||
|
||||
|
||||
def get_agent_prompt(agent_key):
|
||||
"""Liest den System-Prompt eines Agenten aus der Datei."""
|
||||
prompt_file = os.path.join(os.path.dirname(__file__), 'agents', agent_key, 'systemprompt.md')
|
||||
"""Liest den System-Prompt und Persönlichkeit eines Agenten aus den Dateien."""
|
||||
agent_dir = os.path.join(os.path.dirname(__file__), 'agents', agent_key)
|
||||
prompt_file = os.path.join(agent_dir, 'systemprompt.md')
|
||||
personality_file = os.path.join(agent_dir, 'personality.md')
|
||||
|
||||
prompt_content = ""
|
||||
personality_content = ""
|
||||
|
||||
if os.path.exists(prompt_file):
|
||||
with open(prompt_file, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
return ""
|
||||
prompt_content = f.read()
|
||||
|
||||
if os.path.exists(personality_file):
|
||||
with open(personality_file, 'r', encoding='utf-8') as f:
|
||||
personality_content = f.read().strip()
|
||||
|
||||
# Persönlichkeit vor dem System-Prompt einfügen, falls vorhanden
|
||||
if personality_content:
|
||||
return f"{personality_content}\n\n---\n\n{prompt_content}"
|
||||
|
||||
return prompt_content
|
||||
|
||||
|
||||
def execute_agent_task(agent_key, user_prompt, extra_context=""):
|
||||
|
|
@ -191,21 +389,34 @@ def execute_agent_task(agent_key, user_prompt, extra_context=""):
|
|||
if not system_prompt:
|
||||
return f"⚠️ Kein System-Prompt für Agent '{agent_key}' gefunden."
|
||||
|
||||
# Agent-Struktur sicherstellen
|
||||
dirs = ensure_agent_structure(agent_key)
|
||||
work_dir = dirs['work_dir']
|
||||
|
||||
# Memory-Zusammenfassung laden
|
||||
memory_summary = get_agent_memory_summary(agent_key)
|
||||
|
||||
kb_file = os.path.join(os.path.dirname(__file__), 'diversityball_knowledge.md')
|
||||
kb_content = ""
|
||||
if os.path.exists(kb_file):
|
||||
with open(kb_file, 'r', encoding='utf-8') as f:
|
||||
kb_content = f.read()
|
||||
|
||||
# System-Prompt = Agent-Rolle + Wissensdatenbank
|
||||
# System-Prompt = Agent-Rolle + Wissensdatenbank + Memory
|
||||
full_system = f"""{system_prompt}
|
||||
|
||||
## Wissensdatenbank (Diversity-Ball):
|
||||
{kb_content}
|
||||
|
||||
## Deine Erinnerungen:
|
||||
{memory_summary}
|
||||
|
||||
## Wichtig:
|
||||
- Du hast Zugriff auf das Internet via WebFetch-Tool - nutze es aktiv!
|
||||
- Du kannst Emails versenden - nutze send_email wenn beauftragt
|
||||
- Dein Arbeitsverzeichnis: {work_dir}
|
||||
- Speichere ALLE erstellten Dateien in diesem Verzeichnis!
|
||||
- Verwende absolute Pfade für Dateien: {work_dir}/dateiname.ext
|
||||
- Liefere immer eine vollständige, direkt verwertbare Antwort
|
||||
{extra_context}"""
|
||||
|
||||
|
|
@ -222,7 +433,7 @@ def execute_agent_task(agent_key, user_prompt, extra_context=""):
|
|||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300,
|
||||
cwd=os.path.dirname(__file__)
|
||||
cwd=work_dir # Agent arbeitet in seinem work-Verzeichnis
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
|
|
@ -238,7 +449,6 @@ def execute_agent_task(agent_key, user_prompt, extra_context=""):
|
|||
return response_text if response_text else "⚠️ Keine Antwort erhalten."
|
||||
else:
|
||||
return f"⚠️ Fehler: {result.stderr}"
|
||||
return f"⚠️ Fehler: {result.stderr}"
|
||||
except FileNotFoundError:
|
||||
return "⚠️ OpenCode CLI nicht gefunden. Bitte installiere opencode."
|
||||
except subprocess.TimeoutExpired:
|
||||
|
|
@ -820,8 +1030,122 @@ def start_email_poller():
|
|||
logger.info("[TaskWorker] Daemon-Thread gestartet.")
|
||||
|
||||
|
||||
def process_beat_tasks():
|
||||
"""Background-Beat: Verarbeitet offene Tasks automatisch."""
|
||||
logger.info("[TaskBeat] Hintergrund-Thread gestartet.")
|
||||
|
||||
while True:
|
||||
try:
|
||||
pending_tasks = [t for t in tasks if t.get('status') == 'pending' and t.get('type') in ('agent_created', 'manual', 'orchestrated')]
|
||||
|
||||
for task in pending_tasks:
|
||||
agent_key = task.get('agent_key') or task.get('assigned_agent', '')
|
||||
|
||||
if task.get('agent_key') == 'orchestrator':
|
||||
task['status'] = 'in_progress'
|
||||
logger.info("[TaskBeat] Planungsphase für Task #%d", task['id'])
|
||||
|
||||
sub_tasks = task.get('sub_tasks', [])
|
||||
available_agents = task.get('available_agents', list(AGENTS.keys()))
|
||||
|
||||
prompt = f"""Du bist der Master-Orchestrator. Analysiere folgende Tasks und weise sie den richtigen Agenten zu:
|
||||
|
||||
Tasks:
|
||||
{chr(10).join(['- ' + t for t in sub_tasks])}
|
||||
|
||||
Verfügbare Agenten: {', '.join(available_agents)}
|
||||
|
||||
Agent-Beschreibungen:
|
||||
"""
|
||||
for a_key, a_info in AGENTS.items():
|
||||
prompt += f"- {a_key}: {a_info.get('description', 'Keine Beschreibung')[:100]}\n"
|
||||
|
||||
prompt += "\nAntworte in diesem Format (einen Agent pro Task):\n"
|
||||
for i, t in enumerate(sub_tasks):
|
||||
prompt += f"Task {i+1}: [Agent-Key] - Kurze Begründung\n"
|
||||
|
||||
response = execute_agent_task('orchestrator', prompt)
|
||||
|
||||
task['plan_response'] = response
|
||||
|
||||
import re
|
||||
agent_assignments = re.findall(r'Task \d+: (\w+)', response)
|
||||
|
||||
created_sub_tasks = []
|
||||
for i, t in enumerate(sub_tasks):
|
||||
assigned = agent_assignments[i] if i < len(agent_assignments) else available_agents[i % len(available_agents)]
|
||||
if assigned not in AGENTS:
|
||||
assigned = available_agents[0]
|
||||
|
||||
sub_task = {
|
||||
'id': len(tasks) + 1 + len(created_sub_tasks),
|
||||
'title': t[:80],
|
||||
'description': f"Von Orchestrator zugewiesen: {response[:200]}...",
|
||||
'assigned_agent': AGENTS.get(assigned, {}).get('name', assigned),
|
||||
'agent_key': assigned,
|
||||
'status': 'pending',
|
||||
'created': datetime.now().strftime('%Y-%m-%d %H:%M'),
|
||||
'type': 'orchestrated',
|
||||
'parent_task': task['id']
|
||||
}
|
||||
tasks.append(sub_task)
|
||||
created_sub_tasks.append(sub_task['id'])
|
||||
logger.info("[TaskBeat] Sub-Task #%d zugewiesen an %s", sub_task['id'], assigned)
|
||||
|
||||
task['status'] = 'completed'
|
||||
task['sub_task_ids'] = created_sub_tasks
|
||||
logger.info("[TaskBeat] Planungs-Task #%d abgeschlossen. %d Sub-Tasks erstellt.", task['id'], len(created_sub_tasks))
|
||||
continue
|
||||
|
||||
if not agent_key:
|
||||
available_agents = list(AGENTS.keys())
|
||||
if available_agents:
|
||||
agent_key = available_agents[0]
|
||||
task['agent_key'] = agent_key
|
||||
|
||||
if agent_key and agent_key in AGENTS:
|
||||
task['status'] = 'in_progress'
|
||||
logger.info("[TaskBeat] Verarbeite Task #%d – Agent: %s", task['id'], agent_key)
|
||||
|
||||
response = execute_agent_task(agent_key, task.get('title', '') + '\n\n' + task.get('description', ''))
|
||||
|
||||
task['response'] = response
|
||||
|
||||
# Neues Memory-System: Task als strukturierte Erinnerung speichern
|
||||
try:
|
||||
add_agent_memory(agent_key, 'tasks', {
|
||||
'task_id': task['id'],
|
||||
'title': task.get('title', 'Unbekannt'),
|
||||
'description': task.get('description', ''),
|
||||
'result': response,
|
||||
'status': 'completed',
|
||||
'metadata': {
|
||||
'assigned_by': task.get('agent', 'system'),
|
||||
'duration': None
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
logger.warning("[TaskBeat] Konnte Erinnerung nicht speichern: %s", str(e))
|
||||
|
||||
task['status'] = 'completed'
|
||||
logger.info("[TaskBeat] Task #%d abgeschlossen.", task['id'])
|
||||
|
||||
except Exception as e:
|
||||
logger.error("[TaskBeat] Fehler: %s", str(e))
|
||||
|
||||
time.sleep(30)
|
||||
|
||||
|
||||
def start_task_beat():
|
||||
"""Startet den Task-Beat als Daemon-Thread."""
|
||||
beat_thread = threading.Thread(target=process_beat_tasks, name='TaskBeat', daemon=True)
|
||||
beat_thread.start()
|
||||
logger.info("[TaskBeat] Daemon-Thread gestartet.")
|
||||
|
||||
|
||||
# Poller beim App-Start starten
|
||||
start_email_poller()
|
||||
start_task_beat()
|
||||
|
||||
|
||||
@app.route('/')
|
||||
|
|
@ -831,24 +1155,74 @@ def index():
|
|||
|
||||
@app.route('/chat', methods=['GET', 'POST'])
|
||||
def chat():
|
||||
if request.method == 'POST':
|
||||
prompt = request.form.get('prompt', '').strip()
|
||||
agent = request.form.get('agent', '')
|
||||
|
||||
if prompt and agent:
|
||||
response = f"Anfrage an {AGENTS.get(agent, {}).get('name', agent)}: {prompt}"
|
||||
chat_history.append({
|
||||
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M'),
|
||||
'agent': AGENTS.get(agent, {}).get('name', agent),
|
||||
'prompt': prompt,
|
||||
'response': response
|
||||
})
|
||||
session['chat_history'] = chat_history[-20:]
|
||||
flash('Anfrage gesendet!', 'success')
|
||||
# Chat-Verlauf aus Session laden
|
||||
if 'chat_history' not in session:
|
||||
session['chat_history'] = []
|
||||
|
||||
chat_display = session.get('chat_history', [])
|
||||
return render_template('chat.html', agents=AGENTS, chat_history=chat_display)
|
||||
|
||||
|
||||
@app.route('/chat/send', methods=['POST'])
|
||||
def chat_send():
|
||||
"""Führt einen Agent aus und gibt die Antwort per Server-Sent Events zurück."""
|
||||
data = request.get_json()
|
||||
prompt = data.get('prompt', '').strip()
|
||||
agent_key = data.get('agent', '').strip()
|
||||
|
||||
# Validierung vor dem Generator
|
||||
if not prompt or not agent_key:
|
||||
return jsonify({'type': 'error', 'message': 'Fehlende Eingabe'}), 400
|
||||
|
||||
if agent_key not in AGENTS:
|
||||
return jsonify({'type': 'error', 'message': 'Agent nicht gefunden'}), 404
|
||||
|
||||
agent_info = AGENTS.get(agent_key, {})
|
||||
agent_name = agent_info.get('name', agent_key)
|
||||
|
||||
def generate():
|
||||
# Agent-Info senden
|
||||
yield f"data: {json.dumps({'type': 'agent_selected', 'agent': agent_name, 'agent_key': agent_key})}\n\n"
|
||||
yield f"data: {json.dumps({'type': 'processing', 'message': f'⏳ {agent_name} arbeitet...'})}\n\n"
|
||||
|
||||
try:
|
||||
# Agent ausführen (mit Memory und Work-Dir)
|
||||
response = execute_agent_task(agent_key, prompt)
|
||||
|
||||
# Antwort streamen
|
||||
yield f"data: {json.dumps({'type': 'response', 'text': response})}\n\n"
|
||||
|
||||
# Erfolg melden
|
||||
yield f"data: {json.dumps({'type': 'complete', 'message': '✓ Fertig', 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M'), 'response': response})}\n\n"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[Chat] Fehler beim Ausführen von {agent_key}: {str(e)}")
|
||||
yield f"data: {json.dumps({'type': 'error', 'message': str(e)})}\n\n"
|
||||
|
||||
return Response(generate(), mimetype='text/event-stream',
|
||||
headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'})
|
||||
|
||||
|
||||
@app.route('/chat/save', methods=['POST'])
|
||||
def chat_save():
|
||||
"""Speichert eine Chat-Nachricht in der Session."""
|
||||
data = request.get_json()
|
||||
|
||||
if 'chat_history' not in session:
|
||||
session['chat_history'] = []
|
||||
|
||||
session['chat_history'].append({
|
||||
'timestamp': data.get('timestamp'),
|
||||
'agent': data.get('agent'),
|
||||
'agent_key': data.get('agent_key'),
|
||||
'prompt': data.get('prompt'),
|
||||
'response': data.get('response')
|
||||
})
|
||||
session['chat_history'] = session['chat_history'][-30:]
|
||||
session.modified = True
|
||||
|
||||
return jsonify({'success': True})
|
||||
|
||||
@app.route('/tasks', methods=['GET', 'POST'])
|
||||
def task_list():
|
||||
if request.method == 'POST':
|
||||
|
|
@ -1065,7 +1439,18 @@ def agents():
|
|||
flash(f'Daten für "{agent_name}" gespeichert!', 'success')
|
||||
return redirect(url_for('agents', edit=agent_name))
|
||||
|
||||
return render_template('agents.html', agents=AGENTS, agents_list=agents_list, edit_agent=edit_agent, edit_prompt=edit_prompt, edit_reminders=edit_reminders, edit_personality=edit_personality, edit_model=edit_model)
|
||||
# Verfügbare Modelle laden
|
||||
available_models = get_available_models()
|
||||
|
||||
return render_template('agents.html',
|
||||
agents=AGENTS,
|
||||
agents_list=agents_list,
|
||||
edit_agent=edit_agent,
|
||||
edit_prompt=edit_prompt,
|
||||
edit_reminders=edit_reminders,
|
||||
edit_personality=edit_personality,
|
||||
edit_model=edit_model,
|
||||
available_models=available_models)
|
||||
|
||||
|
||||
@app.route('/files', methods=['GET', 'POST'])
|
||||
|
|
@ -1085,8 +1470,19 @@ def files():
|
|||
file_list = get_uploaded_files()
|
||||
email_files = get_email_folder_files()
|
||||
project_files = get_project_files()
|
||||
return render_template('files.html', files=file_list,
|
||||
email_files=email_files, project_files=project_files)
|
||||
|
||||
# Agent Work Folders sammeln
|
||||
agent_work_folders = {}
|
||||
for agent_key in AGENTS.keys():
|
||||
work_files = get_agent_work_files(agent_key)
|
||||
if work_files: # Nur Agenten mit Dateien anzeigen
|
||||
agent_work_folders[agent_key] = work_files
|
||||
|
||||
return render_template('files.html',
|
||||
files=file_list,
|
||||
email_files=email_files,
|
||||
project_files=project_files,
|
||||
agent_work_folders=agent_work_folders)
|
||||
|
||||
|
||||
@app.route('/files/delete/<filename>')
|
||||
|
|
@ -1104,6 +1500,26 @@ def download_file(filename):
|
|||
return send_from_directory(app.config['UPLOAD_FOLDER'], filename, as_attachment=False)
|
||||
|
||||
|
||||
@app.route('/files/agent/<agent_key>/<filename>')
|
||||
def download_agent_file(agent_key, filename):
|
||||
"""Liefert eine Datei aus dem Work-Ordner eines Agenten."""
|
||||
if agent_key not in AGENTS:
|
||||
return jsonify({'error': 'Agent nicht gefunden'}), 404
|
||||
|
||||
dirs = ensure_agent_structure(agent_key)
|
||||
work_dir = dirs['work_dir']
|
||||
filepath = os.path.join(work_dir, filename)
|
||||
|
||||
# Security: Stelle sicher, dass die Datei im work_dir ist
|
||||
if not os.path.abspath(filepath).startswith(os.path.abspath(work_dir)):
|
||||
return jsonify({'error': 'Zugriff verweigert'}), 403
|
||||
|
||||
if not os.path.isfile(filepath):
|
||||
return jsonify({'error': 'Datei nicht gefunden'}), 404
|
||||
|
||||
return send_from_directory(work_dir, filename, as_attachment=False)
|
||||
|
||||
|
||||
@app.route('/files/email/view/<filename>')
|
||||
def view_email_file(filename):
|
||||
"""Gibt Inhalt einer Email-Vorlage als JSON oder direkten Text zurück."""
|
||||
|
|
@ -1326,6 +1742,14 @@ def update_task_api(task_id):
|
|||
return jsonify({'error': 'Task nicht gefunden'}), 404
|
||||
|
||||
|
||||
@app.route('/api/models', methods=['GET'])
|
||||
def get_models():
|
||||
"""Gibt die Liste der verfügbaren KI-Modelle zurück."""
|
||||
force_refresh = request.args.get('refresh', 'false').lower() == 'true'
|
||||
models_data = get_available_models(force_refresh=force_refresh)
|
||||
return jsonify(models_data)
|
||||
|
||||
|
||||
@app.route('/api/agent/<agent_name>/model', methods=['POST'])
|
||||
def set_agent_model(agent_name):
|
||||
"""Setzt das Modell für einen Agenten."""
|
||||
|
|
@ -1389,59 +1813,36 @@ def agent_reminders(agent_name):
|
|||
|
||||
@app.route('/api/orchestrator-distribute', methods=['POST'])
|
||||
def distribute_tasks():
|
||||
"""Verteilt Tasks parallel an mehrere Agenten."""
|
||||
"""Erstellt einen Planungs-Task für den Orchestrator - dieser weist dann die richtigen Agenten zu."""
|
||||
data = request.get_json()
|
||||
tasks_list = data.get('tasks', [])
|
||||
selected_agents = data.get('agents', [])
|
||||
|
||||
if not tasks_list:
|
||||
return jsonify({'error': 'Keine Tasks übergeben'}), 400
|
||||
if not selected_agents:
|
||||
return jsonify({'error': 'Keine Agenten ausgewählt'}), 400
|
||||
|
||||
results = []
|
||||
tasks_text = '\n'.join([f"- {t}" for t in tasks_list])
|
||||
agents_text = ', '.join(selected_agents) if selected_agents else 'alle verfügbaren Agenten'
|
||||
|
||||
def run_task_for_agent(agent_key, task):
|
||||
response = execute_agent_task(agent_key, task)
|
||||
|
||||
reminders_file = os.path.join(os.path.dirname(__file__), 'agents', agent_key, 'reminders.md')
|
||||
if os.path.exists(reminders_file):
|
||||
try:
|
||||
with open(reminders_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M')
|
||||
new_entry = f"\n- {timestamp}: {task[:100]}... - erledigt"
|
||||
|
||||
if '## Letzte Aktionen' in content:
|
||||
content = content.replace('## Letzte Aktionen', f'## Letzte Aktionen{new_entry}')
|
||||
else:
|
||||
content += new_entry
|
||||
|
||||
with open(reminders_file, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
except:
|
||||
pass
|
||||
|
||||
return {
|
||||
'agent': agent_key,
|
||||
'task': task,
|
||||
'response': response[:200] + '...' if len(response) > 200 else response
|
||||
}
|
||||
planning_task = {
|
||||
'id': len(tasks) + 1,
|
||||
'title': f"Planungsphase: {tasks_list[0][:50]}{'...' if len(tasks_list[0]) > 50 else ''}",
|
||||
'description': f"Tasks:\n{tasks_text}\n\nVerfügbare Agenten: {agents_text}\n\nDer Orchestrator soll diese Tasks analysieren und den richtigen Agenten zuweisen.",
|
||||
'assigned_agent': 'Orchestrator',
|
||||
'agent_key': 'orchestrator',
|
||||
'status': 'pending',
|
||||
'created': datetime.now().strftime('%Y-%m-%d %H:%M'),
|
||||
'type': 'orchestrated',
|
||||
'sub_tasks': tasks_list,
|
||||
'available_agents': selected_agents
|
||||
}
|
||||
tasks.append(planning_task)
|
||||
|
||||
import concurrent.futures
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=len(selected_agents)) as executor:
|
||||
futures = []
|
||||
for i, task in enumerate(tasks_list):
|
||||
agent = selected_agents[i % len(selected_agents)]
|
||||
future = executor.submit(run_task_for_agent, agent, task)
|
||||
futures.append(future)
|
||||
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
try:
|
||||
results.append(future.result())
|
||||
except Exception as e:
|
||||
results.append({'error': str(e)})
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Planungs-Task erstellt. Der Orchestrator wird die richtigen Agenten zuweisen.',
|
||||
'tasks': [planning_task['id']]
|
||||
})
|
||||
|
||||
for i, task_text in enumerate(tasks_list):
|
||||
agent_key = selected_agents[i % len(selected_agents)]
|
||||
|
|
@ -1453,17 +1854,24 @@ def distribute_tasks():
|
|||
'title': task_text[:80],
|
||||
'description': 'Automatisch erstellt durch Orchestrator',
|
||||
'assigned_agent': agent_name,
|
||||
'status': 'completed',
|
||||
'status': 'in_progress',
|
||||
'created': datetime.now().strftime('%Y-%m-%d %H:%M'),
|
||||
'type': 'orchestrated',
|
||||
'agent_key': agent_key
|
||||
}
|
||||
tasks.append(new_task)
|
||||
created_tasks.append(new_task['id'])
|
||||
|
||||
executor = concurrent.futures.ThreadPoolExecutor(max_workers=len(selected_agents))
|
||||
for i, task_text in enumerate(tasks_list):
|
||||
agent_key = selected_agents[i % len(selected_agents)]
|
||||
task_id = created_tasks[i]
|
||||
executor.submit(run_task_async, agent_key, task_text, task_id)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Tasks verteilt',
|
||||
'results': results
|
||||
'message': f'{len(created_tasks)} Tasks erstellt und werden im Hintergrund ausgeführt',
|
||||
'tasks': created_tasks
|
||||
})
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue