From 84b2fe3dd78f7da7135bb6c1eee56594cf5a2787 Mon Sep 17 00:00:00 2001 From: pdyde Date: Fri, 20 Feb 2026 22:37:58 +0100 Subject: [PATCH] Add agent reminders, model selection, task distribution and delete functionality --- agent_config.json | 32 +++ agents/budget_manager/reminders.md | 10 + agents/catering_manager/reminders.md | 13 + agents/document_editor/reminders.md | 10 + agents/location_manager/reminders.md | 10 + agents/musik_rechte_advisor/reminders.md | 10 + agents/negotiator/reminders.md | 10 + agents/program_manager/reminders.md | 10 + agents/researcher/reminders.md | 10 + agents/social_media_manager/reminders.md | 10 + agents/tax_advisor/reminders.md | 10 + agents/zusammenfasser/reminders.md | 10 + app.py | 301 +++++++++++++++++++++-- templates/agents.html | 282 ++++++++++++++++----- templates/orchestrator.html | 63 +++++ templates/tasks.html | 42 +++- 16 files changed, 759 insertions(+), 74 deletions(-) create mode 100644 agent_config.json create mode 100644 agents/budget_manager/reminders.md create mode 100644 agents/catering_manager/reminders.md create mode 100644 agents/document_editor/reminders.md create mode 100644 agents/location_manager/reminders.md create mode 100644 agents/musik_rechte_advisor/reminders.md create mode 100644 agents/negotiator/reminders.md create mode 100644 agents/program_manager/reminders.md create mode 100644 agents/researcher/reminders.md create mode 100644 agents/social_media_manager/reminders.md create mode 100644 agents/tax_advisor/reminders.md create mode 100644 agents/zusammenfasser/reminders.md diff --git a/agent_config.json b/agent_config.json new file mode 100644 index 0000000..1bf55ff --- /dev/null +++ b/agent_config.json @@ -0,0 +1,32 @@ +{ + "researcher": { + "model": "opencode/big-pickle" + }, + "location_manager": { + "model": "opencode/big-pickle" + }, + "catering_manager": { + "model": "opencode/big-pickle" + }, + "program_manager": { + "model": "opencode/big-pickle" + }, + "document_editor": { + "model": "opencode/big-pickle" + }, + "tax_advisor": { + "model": "opencode/big-pickle" + }, + "musik_rechte_advisor": { + "model": "opencode/big-pickle" + }, + "zusammenfasser": { + "model": "opencode/big-pickle" + }, + "social_media_manager": { + "model": "opencode/big-pickle" + }, + "negotiator": { + "model": "opencode/big-pickle" + } +} \ No newline at end of file diff --git a/agents/budget_manager/reminders.md b/agents/budget_manager/reminders.md new file mode 100644 index 0000000..ef9e3f2 --- /dev/null +++ b/agents/budget_manager/reminders.md @@ -0,0 +1,10 @@ +# Erinnerungen - Budget_Manager + +## Aktuelle Tasks +- + +## Notizen +- + +## Letzte Aktionen +- \ No newline at end of file diff --git a/agents/catering_manager/reminders.md b/agents/catering_manager/reminders.md new file mode 100644 index 0000000..4603f29 --- /dev/null +++ b/agents/catering_manager/reminders.md @@ -0,0 +1,13 @@ +# Erinnerungen - Catering Manager + +## Aktuelle Tasks +- + +## Notizen +- + +## Letzte Aktionen +- 2026-02-20 22:02: ich brauche einen durchgetakteten plan für das event. musik, essen etc.... - erledigt +- 2026-02-20 21:57: erstelle einen ablaufplan für unser event... - erledigt +- 2026-02-20 21:53: erstelle mir einen plan für unser Event... - erledigt +- diff --git a/agents/document_editor/reminders.md b/agents/document_editor/reminders.md new file mode 100644 index 0000000..676d6eb --- /dev/null +++ b/agents/document_editor/reminders.md @@ -0,0 +1,10 @@ +# Erinnerungen - Document Editor + +## Aktuelle Tasks +- + +## Notizen +- + +## Letzte Aktionen +- diff --git a/agents/location_manager/reminders.md b/agents/location_manager/reminders.md new file mode 100644 index 0000000..aa9915f --- /dev/null +++ b/agents/location_manager/reminders.md @@ -0,0 +1,10 @@ +# Erinnerungen - Location Manager + +## Aktuelle Tasks +- + +## Notizen +- + +## Letzte Aktionen +- diff --git a/agents/musik_rechte_advisor/reminders.md b/agents/musik_rechte_advisor/reminders.md new file mode 100644 index 0000000..cc0d24f --- /dev/null +++ b/agents/musik_rechte_advisor/reminders.md @@ -0,0 +1,10 @@ +# Erinnerungen - Musik Rechte Advisor + +## Aktuelle Tasks +- + +## Notizen +- + +## Letzte Aktionen +- diff --git a/agents/negotiator/reminders.md b/agents/negotiator/reminders.md new file mode 100644 index 0000000..0e3f425 --- /dev/null +++ b/agents/negotiator/reminders.md @@ -0,0 +1,10 @@ +# Erinnerungen - Negotiator + +## Aktuelle Tasks +- + +## Notizen +- + +## Letzte Aktionen +- diff --git a/agents/program_manager/reminders.md b/agents/program_manager/reminders.md new file mode 100644 index 0000000..6a7f2aa --- /dev/null +++ b/agents/program_manager/reminders.md @@ -0,0 +1,10 @@ +# Erinnerungen - Program Manager + +## Aktuelle Tasks +- + +## Notizen +- + +## Letzte Aktionen +- diff --git a/agents/researcher/reminders.md b/agents/researcher/reminders.md new file mode 100644 index 0000000..3da9b6f --- /dev/null +++ b/agents/researcher/reminders.md @@ -0,0 +1,10 @@ +# Erinnerungen - Researcher + +## Aktuelle Tasks +- + +## Notizen +- + +## Letzte Aktionen +- diff --git a/agents/social_media_manager/reminders.md b/agents/social_media_manager/reminders.md new file mode 100644 index 0000000..e17e232 --- /dev/null +++ b/agents/social_media_manager/reminders.md @@ -0,0 +1,10 @@ +# Erinnerungen - Social Media Manager + +## Aktuelle Tasks +- + +## Notizen +- + +## Letzte Aktionen +- diff --git a/agents/tax_advisor/reminders.md b/agents/tax_advisor/reminders.md new file mode 100644 index 0000000..6a2d35e --- /dev/null +++ b/agents/tax_advisor/reminders.md @@ -0,0 +1,10 @@ +# Erinnerungen - Tax Advisor + +## Aktuelle Tasks +- + +## Notizen +- + +## Letzte Aktionen +- diff --git a/agents/zusammenfasser/reminders.md b/agents/zusammenfasser/reminders.md new file mode 100644 index 0000000..2dd44dd --- /dev/null +++ b/agents/zusammenfasser/reminders.md @@ -0,0 +1,10 @@ +# Erinnerungen - Zusammenfasser + +## Aktuelle Tasks +- + +## Notizen +- + +## Letzte Aktionen +- diff --git a/app.py b/app.py index bb180fe..46843ee 100644 --- a/app.py +++ b/app.py @@ -18,6 +18,33 @@ from dotenv import load_dotenv load_dotenv() +# ── Agent Konfiguration ─────────────────────────────────────────────────────── +AGENT_CONFIG_FILE = os.path.join(os.path.dirname(__file__), 'agent_config.json') + +def get_agent_config(): + """Lädt die Agentenkonfiguration aus der JSON-Datei.""" + if os.path.exists(AGENT_CONFIG_FILE): + try: + with open(AGENT_CONFIG_FILE, 'r', encoding='utf-8') as f: + return json.load(f) + except: + pass + return {} + +def get_agent_model(agent_key): + """Gibt das konfigurierte Modell für einen Agenten zurück.""" + config = get_agent_config() + return config.get(agent_key, {}).get('model', 'opencode/big-pickle') + +def save_agent_config(agent_key, model): + """Speichert die Konfiguration für einen Agenten.""" + config = get_agent_config() + if agent_key not in config: + config[agent_key] = {} + config[agent_key]['model'] = model + with open(AGENT_CONFIG_FILE, 'w', encoding='utf-8') as f: + json.dump(config, f, indent=2) + # ── Email-Journal (SQLite) ────────────────────────────────────────────────── # Speichert jede gesehene Email mit Message-ID und Verarbeitungsstatus. # Verhindert, dass Emails verloren gehen wenn die App abstürzt. @@ -185,10 +212,13 @@ def execute_agent_task(agent_key, user_prompt, extra_context=""): # System-Prompt + User-Prompt zusammen als eine Message # (--prompt flag gibt leere Antwort, daher alles in eine Message) combined_message = f"{full_system}\n\n---\n\n{user_prompt}" - + + # Modell aus Konfiguration holen + model = get_agent_model(agent_key) + try: result = subprocess.run( - ['opencode', 'run', '--format', 'json', combined_message], + ['opencode', 'run', '--model', model, '--format', 'json', combined_message], capture_output=True, text=True, timeout=300, @@ -887,8 +917,9 @@ def agent_stream(): yield f"data: {json.dumps({'type': 'processing', 'message': f'⏳ {agent_name} arbeitet...'})}\n\n" try: + model = get_agent_model(selected_agent) proc = subprocess.Popen( - ['opencode', 'run', '--format', 'json', full_prompt], + ['opencode', 'run', '--model', model, '--format', 'json', full_prompt], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, @@ -958,35 +989,83 @@ def agents(): if os.path.exists(agents_dir): for agent_name in sorted(os.listdir(agents_dir)): - prompt_file = os.path.join(agents_dir, agent_name, 'systemprompt.md') - if os.path.isdir(os.path.join(agents_dir, agent_name)) and os.path.exists(prompt_file): - with open(prompt_file, 'r', encoding='utf-8') as f: - prompt_content = f.read() + agent_path = os.path.join(agents_dir, agent_name) + prompt_file = os.path.join(agent_path, 'systemprompt.md') + reminders_file = os.path.join(agent_path, 'reminders.md') + personality_file = os.path.join(agent_path, 'personality.md') + + if os.path.isdir(agent_path): + prompt_content = '' + reminders_content = '' + personality_content = '' + + if os.path.exists(prompt_file): + with open(prompt_file, 'r', encoding='utf-8') as f: + prompt_content = f.read() + + if os.path.exists(reminders_file): + with open(reminders_file, 'r', encoding='utf-8') as f: + reminders_content = f.read() + else: + reminders_content = '# Erinnerungen - ' + agent_name.title() + '\n\n## Aktuelle Tasks\n-\n\n## Notizen\n- \n\n## Letzte Aktionen\n- ' + + if os.path.exists(personality_file): + with open(personality_file, 'r', encoding='utf-8') as f: + personality_content = f.read() + agents_list.append({ 'name': agent_name, - 'prompt': prompt_content + 'prompt': prompt_content, + 'reminders': reminders_content, + 'personality': personality_content }) edit_agent = request.args.get('edit') + + if not edit_agent and agents_list: + return redirect(url_for('agents', edit=agents_list[0]['name'])) + edit_prompt = '' + edit_reminders = '' + edit_personality = '' + edit_model = 'opencode/big-pickle' if edit_agent: for agent in agents_list: if agent['name'] == edit_agent: edit_prompt = agent['prompt'] + edit_reminders = agent['reminders'] + edit_personality = agent['personality'] break + edit_model = get_agent_model(edit_agent) if request.method == 'POST': agent_name = request.form.get('agent_name', '').strip() prompt_content = request.form.get('prompt_content', '') + reminders_content = request.form.get('reminders_content', '') + personality_content = request.form.get('personality_content', '') - if agent_name and prompt_content is not None: - prompt_file = os.path.join(agents_dir, agent_name, 'systemprompt.md') - with open(prompt_file, 'w', encoding='utf-8') as f: - f.write(prompt_content) - flash(f'System-Prompt für "{agent_name}" gespeichert!', 'success') - return redirect(url_for('agents')) + if agent_name: + agent_path = os.path.join(agents_dir, agent_name) + + if prompt_content is not None: + prompt_file = os.path.join(agent_path, 'systemprompt.md') + with open(prompt_file, 'w', encoding='utf-8') as f: + f.write(prompt_content) + + if reminders_content is not None: + reminders_file = os.path.join(agent_path, 'reminders.md') + with open(reminders_file, 'w', encoding='utf-8') as f: + f.write(reminders_content) + + if personality_content is not None: + personality_file = os.path.join(agent_path, 'personality.md') + with open(personality_file, 'w', encoding='utf-8') as f: + f.write(personality_content) + + 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) + 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) @app.route('/files', methods=['GET', 'POST']) @@ -1196,5 +1275,197 @@ def journal_clear(): return redirect(url_for('settings')) +# ── Task API ──────────────────────────────────────────────────────────────── +@app.route('/api/tasks', methods=['GET', 'POST']) +def api_tasks(): + """API zum Erstellen und Abrufen von Tasks.""" + if request.method == 'POST': + data = request.get_json() + title = data.get('title', '').strip() + description = data.get('description', '') + assigned_agent = data.get('assigned_agent', '') + agent_key = data.get('agent_key', '') + + if not title: + return jsonify({'error': 'Kein Titel übergeben'}), 400 + + task_id = len(tasks) + 1 + while any(t['id'] == task_id for t in tasks): + task_id += 1 + + new_task = { + 'id': task_id, + 'title': title, + 'description': description, + 'assigned_agent': AGENTS.get(assigned_agent, {}).get('name', assigned_agent) if assigned_agent else 'Nicht zugewiesen', + 'agent_key': agent_key or assigned_agent, + 'status': 'pending', + 'created': datetime.now().strftime('%Y-%m-%d %H:%M'), + 'type': 'agent_created', + 'created_by': agent_key + } + tasks.append(new_task) + + return jsonify({'success': True, 'task': new_task}) + + task_list = list(reversed(tasks)) + return jsonify({'tasks': task_list}) + + +@app.route('/api/tasks/', methods=['PUT']) +def update_task_api(task_id): + """API zum Aktualisieren eines Tasks.""" + data = request.get_json() + new_status = data.get('status') + + for task in tasks: + if task['id'] == task_id: + task['status'] = new_status + return jsonify({'success': True, 'task': task}) + + return jsonify({'error': 'Task nicht gefunden'}), 404 + + +@app.route('/api/agent//model', methods=['POST']) +def set_agent_model(agent_name): + """Setzt das Modell für einen Agenten.""" + data = request.get_json() + if data and 'model' in data: + save_agent_config(agent_name, data['model']) + return jsonify({'success': True, 'message': f'Modell für {agent_name} gesetzt auf {data["model"]}'}) + return jsonify({'error': 'Kein Modell übergeben'}), 400 + + +@app.route('/api/agent//delete', methods=['DELETE']) +def delete_agent(agent_name): + """Löscht einen Agenten (den gesamten Ordner).""" + import shutil + + agents_dir = os.path.join(os.path.dirname(__file__), 'agents') + agent_path = os.path.join(agents_dir, agent_name) + + if not os.path.isdir(agent_path): + return jsonify({'error': 'Agent nicht gefunden'}), 404 + + try: + shutil.rmtree(agent_path) + + config = get_agent_config() + if agent_name in config: + del config[agent_name] + with open(AGENT_CONFIG_FILE, 'w', encoding='utf-8') as f: + json.dump(config, f, indent=2) + + return jsonify({'success': True, 'message': f'Agent "{agent_name}" wurde gelöscht.'}) + except Exception as e: + return jsonify({'error': str(e)}), 500 + + +@app.route('/api/agent//reminders', methods=['GET', 'POST']) +def agent_reminders(agent_name): + agents_dir = os.path.join(os.path.dirname(__file__), 'agents') + agent_path = os.path.join(agents_dir, agent_name) + reminders_file = os.path.join(agent_path, 'reminders.md') + + if not os.path.isdir(agent_path): + return jsonify({'error': 'Agent nicht gefunden'}), 404 + + if request.method == 'GET': + if os.path.exists(reminders_file): + with open(reminders_file, 'r', encoding='utf-8') as f: + content = f.read() + else: + content = '' + return jsonify({'reminders': content}) + + if request.method == 'POST': + data = request.get_json() + if data and 'reminders' in data: + with open(reminders_file, 'w', encoding='utf-8') as f: + f.write(data['reminders']) + return jsonify({'success': True, 'message': 'Erinnerungen gespeichert'}) + return jsonify({'error': 'Keine Daten übergeben'}), 400 + + +@app.route('/api/orchestrator-distribute', methods=['POST']) +def distribute_tasks(): + """Verteilt Tasks parallel an mehrere Agenten.""" + 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 = [] + + 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 + } + + 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)}) + + for i, task_text in enumerate(tasks_list): + agent_key = selected_agents[i % len(selected_agents)] + agent_info = AGENTS.get(agent_key, {}) + agent_name = agent_info.get('name', agent_key) + + new_task = { + 'id': len(tasks) + 1 + i, + 'title': task_text[:80], + 'description': 'Automatisch erstellt durch Orchestrator', + 'assigned_agent': agent_name, + 'status': 'completed', + 'created': datetime.now().strftime('%Y-%m-%d %H:%M'), + 'type': 'orchestrated', + 'agent_key': agent_key + } + tasks.append(new_task) + + return jsonify({ + 'success': True, + 'message': 'Tasks verteilt', + 'results': results + }) + + if __name__ == '__main__': app.run(debug=False, host='0.0.0.0', port=5000, threaded=True) diff --git a/templates/agents.html b/templates/agents.html index dd8da46..2e51eef 100644 --- a/templates/agents.html +++ b/templates/agents.html @@ -4,72 +4,240 @@ {% block content %} -{% if edit_agent %} -
-
-
Prompt bearbeiten: {{ edit_agent }}
-
-
-
- -
- - +
+
+
+
+
Agenten
-
- - Abbrechen +
+ {% for agent in agents_list %} + + {{ agent.name.replace('_', ' ').title() }} + + {% endfor %}
- +
-
- -{% else %} -
-
-
Alle Agenten
- {{ agents_list|length }} -
-
- {% if agents_list %} -
- - - - - - - - - - {% for agent in agents_list %} - - - - - - {% endfor %} - -
AgentPrompt-VorschauAktionen
- {{ agent.name }} - - - {{ agent.prompt[:160] }}{% if agent.prompt|length > 160 %}…{% endif %} - - - Bearbeiten -
+ +
+ {% if edit_agent %} +
+
+
Bearbeiten: {{ edit_agent.replace('_', ' ').title() }}
+
+
+
+ + + + +
+
+
+ + +
+
+ +
+
+ +
+ Hier kannst du manuell Notizen hinzufügen. Agenten können diese Datei auch selbst beschreiben. +
+ +
+
+ +
+
+ +
+ Definiere die Persönlichkeitszüge des Agenten. +
+ +
+
+ +
+
+ +
+ Wähle das KI-Modell, das dieser Agent verwendet. +
+ +
+
+
+ +
+
+
⚠️ Danger Zone
+

Hier kannst du diesen Agenten dauerhaft löschen. Diese Aktion kann nicht rückgängig gemacht werden!

+

Alle Dateien im Ordner agents/{{ edit_agent }}/ werden gelöscht.

+ +
+
+
+
+ +
+ + Abbrechen +
+
+
{% else %} -
- Keine Agenten gefunden. Stelle sicher, dass agents/ Unterverzeichnisse mit systemprompt.md enthält. +
+
+
Alle Agenten
+ {{ agents_list|length }} +
+
+ {% if agents_list %} +
+ + + + + + + + + + {% for agent in agents_list %} + + + + + + {% endfor %} + +
AgentPrompt-VorschauAktionen
+ {{ agent.name.replace('_', ' ').title() }} + + + {{ agent.prompt[:160] }}{% if agent.prompt|length > 160 %}…{% endif %} + + + Bearbeiten +
+
+ {% else %} +
+ Keine Agenten gefunden. Stelle sicher, dass agents/ Unterverzeichnisse mit systemprompt.md enthält. +
+ {% endif %} +
{% endif %}
-{% endif %} +{% endblock %} + +{% block scripts %} + {% endblock %} diff --git a/templates/orchestrator.html b/templates/orchestrator.html index 0707e24..497f314 100644 --- a/templates/orchestrator.html +++ b/templates/orchestrator.html @@ -16,6 +16,32 @@
+ +
+
+
📋 Tasks verteilen
+
+
+
+ + +
+
+ +
+ {% for key, agent in agents.items() %} +
+ + +
+ {% endfor %} +
+
+ +
+
+
+
Prompt eingeben
@@ -153,5 +179,42 @@ function sendPromptWithStream() { streamBtn.textContent = 'Live-Antwort anfordern'; }); } + +function distributeTodos() { + const prompt = document.getElementById('todoPrompt').value.trim(); + if (!prompt) { alert('Bitte Aufgaben eingeben!'); return; } + + const selectedAgents = Array.from(document.querySelectorAll('.agent-checkbox:checked')).map(cb => cb.value); + if (selectedAgents.length === 0) { alert('Bitte mindestens einen Agenten auswählen!'); return; } + + const status = document.getElementById('todoStatus'); + status.textContent = 'Starte parallele Ausführung...'; + status.className = 'form-text mt-2 text-info'; + + const tasks = prompt.split('\n').filter(t => t.trim()); + + fetch('/api/orchestrator-distribute', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ tasks, agents: selectedAgents }) + }) + .then(r => r.json()) + .then(data => { + if (data.success) { + status.textContent = '✓ ' + data.message + ' - ' + data.results.length + ' Tasks gestartet'; + status.className = 'form-text mt-2 text-success'; + if (data.results && data.results.length > 0) { + location.reload(); + } + } else { + status.textContent = 'Fehler: ' + data.error; + status.className = 'form-text mt-2 text-danger'; + } + }) + .catch(err => { + status.textContent = 'Fehler: ' + err.message; + status.className = 'form-text mt-2 text-danger'; + }); +} {% endblock %} diff --git a/templates/tasks.html b/templates/tasks.html index 1ae575d..3b5be54 100644 --- a/templates/tasks.html +++ b/templates/tasks.html @@ -4,12 +4,12 @@ {% block content %}
-
+
Neuen Task erstellen
@@ -37,6 +37,18 @@
+ +
+
+
🔄 Auto-Refresh
+
+
+
+ + +
+
+
@@ -68,6 +80,12 @@ {% if task.type == 'email' %} Email {% endif %} + {% if task.type == 'orchestrated' %} + Orchestriert + {% endif %} + {% if task.type == 'agent_created' %} + Agent + {% endif %} {% if task.description %}
{{ task.description[:60] }}{% if task.description|length > 60 %}…{% endif %} @@ -116,3 +134,23 @@
{% endblock %} + +{% block scripts %} + +{% endblock %}