Compare commits

..

No commits in common. "320a1d4d879613305d1db8a7909a7e7290262b97" and "632a6c253a76356fd413ac77bccec8ab56da0c57" have entirely different histories.

2 changed files with 146 additions and 353 deletions

107
README.md
View file

@ -15,9 +15,7 @@ Frankenbot ist ein intelligentes Event-Management-System, bei dem spezialisierte
- **Dynamische Modellauswahl**: 29+ KI-Modelle verfügbar (OpenCode, Anthropic, Ollama)
### 📋 Task-Management
- **Persistente Speicherung**: SQLite-Datenbank für Tasks
- **Automatische Task-Verarbeitung**: TaskBeat verarbeitet Tasks alle 10 Sekunden
- **Auto-Cleanup**: Löscht completed Tasks automatisch nach 7 Tagen
- **Auto-Refresh UI**: Tasks-Seite aktualisiert sich automatisch alle 15 Sekunden
- **Task-Delegation**: Orchestrator analysiert Tasks und weist sie passenden Agents zu
- **Status-Tracking**: pending → in_progress → completed
@ -31,9 +29,7 @@ Frankenbot ist ein intelligentes Event-Management-System, bei dem spezialisierte
- **IMAP**: Email-Posteingang überwachen
- **SMTP**: Emails versenden
- **Email-to-Task**: Automatische Task-Erstellung aus Emails
- **Whitelist-System**: Nur autorisierte Absender (diversityball.at Domain)
- **Intelligente Vorstellung**: Neue Absender werden automatisch gebeten sich vorzustellen
- **Team-Member Lernsystem**: Orchestrator baut automatisch Wissensdatenbank auf
- **Whitelist-System**: Nur autorisierte Absender
### 📁 Datei-Management
- **Agent Work-Folders**: Jeder Agent hat eigenes Arbeitsverzeichnis
@ -51,21 +47,6 @@ Frankenbot ist ein intelligentes Event-Management-System, bei dem spezialisierte
- **On-Demand Zugriff**: Agents nutzen `@READ_KNOWLEDGE` statt volle KB im Prompt
- **Performance-Optimierung**: Reduziert Prompt-Größe um ~15KB
- **Event-spezifisch**: Diversity-Ball Wien 2026 Informationen
- **Self-Learning**: System lernt automatisch über Team-Members und Verantwortlichkeiten
### 👥 Team-Management
- **Team-Members Datenbank**: Reale Mitarbeiter mit Rollen und Verantwortlichkeiten
- **Automatisches Lernen**: Neue Absender werden erkannt und vorgestellt
- **Orchestrator Beat**: Prüft alle 30 Minuten Tasks und eskaliert bei Bedarf
- **Direkter Kontakt**: Orchestrator kann Team-Members per Email/Telegram kontaktieren
- **Intelligente Koordination**: Weiß wer wofür verantwortlich ist
### 💬 Telegram-Integration
- **Bidirektionale Kommunikation**: Nachrichten senden und empfangen
- **QR-Code Setup**: Einfache Bot-Verbindung über Settings-Seite
- **Task-Erstellung**: Telegram-Nachrichten werden automatisch zu Tasks
- **Antworten**: Agent-Responses werden zurück zu Telegram gesendet
- **User-Whitelist**: Nur autorisierte Telegram-User
## 🚀 Installation
@ -138,42 +119,14 @@ Reason: Für große Events mit 500+ Gästen benötigt
@END
```
#### Team-Member kontaktieren
```
@SEND_EMAIL
To: georg.tschare@signtime.media
Subject: Budget-Freigabe benötigt
Body: Hallo Georg, für das Catering benötigen wir eine Budget-Freigabe...
@END
```
#### Team-Member aktualisieren
```
@UPDATE_TEAM_MEMBER
Identifier: p.dyderski@live.at
TelegramID: 123456789
Responsibilities: System-Administration, 3D-Visualisierung, AI-Agenten, R&D
@END
```
#### Neues Team-Mitglied hinzufügen
```
@ADD_TEAM_MEMBER
Name: Max Mustermann
Role: Marketing Coordinator
Responsibilities: Social Media, Content Creation
Email: max@diversityball.at
@END
```
## 🗂️ Projekt-Struktur
```
frankenbot/
├── app.py # Haupt-Flask-App (2900+ Zeilen)
├── app.py # Haupt-Flask-App (2128 Zeilen)
├── agent_config.json # Agent-Modell-Zuweisungen
├── diversityball_knowledge.md # Event-Wissensdatenbank
├── email_journal.db # SQLite-Datenbank (Emails, Tasks, Team-Members)
├── email_journal.db # SQLite-Datenbank (Emails)
├── agents/ # Agent-Verzeichnisse
│ ├── orchestrator/
@ -221,11 +174,10 @@ frankenbot/
- **Backend**: Flask 3.x
- **Frontend**: Bootstrap 5, Vanilla JavaScript
- **Database**: SQLite (Email-Journal, Tasks, Team-Members)
- **AI**: OpenCode CLI (Multi-Provider Support: Anthropic, Ollama, etc.)
- **Database**: SQLite (Email-Journal)
- **AI**: OpenCode CLI (Multi-Provider Support)
- **Templates**: Jinja2
- **Streaming**: Server-Sent Events (SSE)
- **Bot**: python-telegram-bot, QR-Code Generation
## 🎨 UI-Routen
@ -236,9 +188,8 @@ frankenbot/
| `/orchestrator` | Orchestrator-Chat mit Task-Verteilung |
| `/tasks` | Task-Management mit Auto-Refresh |
| `/agents` | Agent-Verwaltung (Erstellen, Bearbeiten, Modell-Auswahl) |
| `/files` | Datei-Manager (Upload, Download, Agent Work-Folders, View) |
| `/files` | Datei-Manager (Upload, Download, Agent Work-Folders) |
| `/emails` | Email-Interface (Senden, Empfangen, Vorlagen) |
| `/settings` | System-Einstellungen, Telegram QR-Code, Poller-Konfiguration |
## ⚙️ Konfiguration
@ -261,25 +212,10 @@ EMAIL_IMAP_SERVER=imap.gmail.com
EMAIL_SMTP_SERVER=smtp.gmail.com
```
### Telegram-Konfiguration
Bearbeite `.env`:
```bash
TELEGRAM_BOT_TOKEN=your-bot-token-here
TELEGRAM_ALLOWED_USERS=123456789,987654321
TELEGRAM_BOT_USERNAME=your_bot_name_bot
```
Setup-Anleitung:
1. Erstelle Bot via [@BotFather](https://t.me/BotFather)
2. Hole deine User-ID via [@userinfobot](https://t.me/userinfobot)
3. QR-Code scannen auf `/settings` Seite
### Background-Threads
- **EmailPoller**: Prüft alle 2 Minuten auf neue Emails
- **TaskBeat**: Verarbeitet Tasks alle 10 Sekunden
- **TaskWorker**: Führt Agent-Tasks aus (Timeout: 10 Min)
- **OrchestratorBeat**: Prüft alle 30 Minuten auf blockierte Tasks ⭐ NEU
- **TelegramBot**: Polling für Telegram-Nachrichten (wenn konfiguriert)
## 📊 Performance
@ -288,9 +224,6 @@ Setup-Anleitung:
- ✅ Model-Cache (1h TTL)
- ✅ Agent arbeiten in eigenen work-Verzeichnissen
- ✅ Timeout: 600 Sekunden (10 Min) für komplexe Tasks
- ✅ Task-Cleanup: Auto-Löschung nach 7 Tagen
- ✅ Persistente Tasks in SQLite-Datenbank
- ✅ Self-Learning Team-Management
### Geschätzte Ausführungszeiten
- Einfache Tasks: 30-60 Sekunden
@ -300,12 +233,10 @@ Setup-Anleitung:
## 🔒 Security
### Implementierte Maßnahmen
- ✅ XSS-Schutz: HTML-Escaping in allen Templates (orchestrator.html, chat.html)
- ✅ Email-Whitelist: Nur autorisierte Absender (diversityball.at Domain)
- ✅ Telegram-Whitelist: Nur autorisierte User-IDs
- ✅ XSS-Schutz: HTML-Escaping in allen Templates
- ✅ Email-Whitelist: Nur autorisierte Absender
- ✅ Exception-Handling: Spezifische Exception-Typen mit Logging
- ✅ Request-Validierung: `.get()` mit Defaults
- ✅ Path Traversal Protection: Agent-Files nur aus eigenem work-Verzeichnis
- ✅ Session-basierte Auth: Flask-Sessions
### Empfehlungen für Produktion
@ -380,23 +311,6 @@ git push origin main
3. TaskBeat weist Task zu passendem Agent
4. Agent arbeitet Task ab und antwortet per Email
### Neuer Team-Member (Self-Learning)
1. Email von unbekanntem @diversityball.at Absender
2. System erkennt: "Nicht in Team-DB!"
3. Orchestrator übernimmt automatisch
4. Begrüßt und bittet um Vorstellung (Name, Rolle, Verantwortlichkeiten)
5. Beantwortet auch die eigentliche Anfrage
6. Nutzt @ADD_TEAM_MEMBER zum Speichern
7. Zukünftig: Bessere Koordination durch bekannte Verantwortlichkeiten
### Orchestrator Beat Monitoring
1. Alle 30 Minuten: System-Check
2. Findet Tasks ohne Fortschritt (>2h pending)
3. Findet blockierte Tasks (>4h in_progress)
4. Fragt Orchestrator was zu tun ist
5. Orchestrator entscheidet ob Eskalation nötig
6. Bei Bedarf: @SEND_EMAIL an verantwortliches Team-Mitglied
### Wissensdatenbank-Abfrage
1. Agent braucht Info (z.B. Budget)
2. Nutzt `@READ_KNOWLEDGE Topic: Budget`
@ -413,7 +327,6 @@ Dieses Projekt ist für internen Gebrauch und Event-Management entwickelt.
---
**Version**: 2.0.0
**Letztes Update**: 21. Februar 2026
**Version**: 1.0.0
**Letztes Update**: Februar 2026
**Status**: Production-Ready ✅
**Neue Features**: Self-Learning Team-Management, Orchestrator Beat, Task-Persistierung, Telegram-Integration

390
app.py
View file

@ -884,9 +884,10 @@ def parse_agent_commands(agent_key, response_text):
)
for question, context in ask_requests:
# Erstelle Task für Orchestrator um die Frage zu beantworten
task_id = create_task(
title=f"Frage von {agent_key}: {question.strip()[:80]}",
description=f"""Ein Agent braucht Hilfe!
new_task = {
'id': len(tasks) + 1,
'title': f"Frage von {agent_key}: {question.strip()[:80]}",
'description': f"""Ein Agent braucht Hilfe!
**Von:** {agent_key}
**Frage:** {question.strip()}
@ -894,13 +895,16 @@ def parse_agent_commands(agent_key, response_text):
Bitte beantworte die Frage oder delegiere an den passenden Experten-Agent.
Die Antwort wird an {agent_key} zurückgegeben.""",
agent_key='orchestrator',
task_type='agent_question',
created_by=agent_key,
from_agent=agent_key,
return_to=agent_key
)
logger.info(f"[AgentCmd] {agent_key} fragt Orchestrator (Task #{task_id}): {question.strip()[:50]}")
'assigned_agent': 'Orchestrator',
'agent_key': 'orchestrator',
'status': 'pending',
'created': datetime.now().strftime('%Y-%m-%d %H:%M'),
'type': 'agent_question',
'from_agent': agent_key,
'return_to': agent_key
}
tasks.append(new_task)
logger.info(f"[AgentCmd] {agent_key} fragt Orchestrator: {question.strip()[:50]}")
# CREATE_SUBTASK: Agent möchte Subtask erstellen
subtask_requests = re.findall(
@ -909,15 +913,19 @@ Die Antwort wird an {agent_key} zurückgegeben.""",
re.DOTALL
)
for task_desc, requirements in subtask_requests:
task_id = create_task(
title=task_desc.strip()[:100],
description=f"Von {agent_key} angefordert:\n{requirements.strip()}",
agent_key='orchestrator',
task_type='agent_subtask',
created_by=agent_key,
from_agent=agent_key
)
logger.info(f"[AgentCmd] {agent_key} erstellt Subtask #{task_id} via Orchestrator")
new_task = {
'id': len(tasks) + 1,
'title': task_desc.strip()[:100],
'description': f"Von {agent_key} angefordert:\n{requirements.strip()}",
'assigned_agent': 'Orchestrator',
'agent_key': 'orchestrator',
'status': 'pending',
'created': datetime.now().strftime('%Y-%m-%d %H:%M'),
'type': 'agent_subtask',
'from_agent': agent_key
}
tasks.append(new_task)
logger.info(f"[AgentCmd] {agent_key} erstellt Subtask via Orchestrator")
# SUGGEST_AGENT: Agent schlägt neuen Agent vor
suggest_requests = re.findall(
@ -930,24 +938,28 @@ Die Antwort wird an {agent_key} zurückgegeben.""",
agent_key_suggestion = role.lower().replace(' ', '_').replace('-', '_')
# Task für Orchestrator um Agent zu erstellen
task_id = create_task(
title=f"Agent-Vorschlag: {role.strip()}",
description=f"""Agent {agent_key} schlägt vor, einen neuen Agent zu erstellen:
new_task = {
'id': len(tasks) + 1,
'title': f"Agent-Vorschlag: {role.strip()}",
'description': f"""Agent {agent_key} schlägt vor, einen neuen Agent zu erstellen:
**Rolle:** {role.strip()}
**Fähigkeiten:** {skills.strip()}
**Begründung:** {reason.strip()}
Bitte entscheide ob dieser Agent erstellt werden soll.""",
agent_key='orchestrator',
task_type='agent_suggestion',
created_by=agent_key,
from_agent=agent_key,
suggested_agent=agent_key_suggestion,
suggested_role=role.strip(),
suggested_skills=skills.strip()
)
logger.info(f"[AgentCmd] {agent_key} schlägt neuen Agent vor (Task #{task_id}): {role.strip()}")
'assigned_agent': 'Orchestrator',
'agent_key': 'orchestrator',
'status': 'pending',
'created': datetime.now().strftime('%Y-%m-%d %H:%M'),
'type': 'agent_suggestion',
'from_agent': agent_key,
'suggested_agent': agent_key_suggestion,
'suggested_role': role.strip(),
'suggested_skills': skills.strip()
}
tasks.append(new_task)
logger.info(f"[AgentCmd] {agent_key} schlägt neuen Agent vor: {role.strip()}")
# READ_KNOWLEDGE: Agent möchte Wissensdatenbank durchsuchen
read_kb_requests = re.findall(
@ -978,124 +990,6 @@ Bitte entscheide ob dieser Agent erstellt werden soll.""",
if relevant_sections:
logger.info(f"[AgentCmd] {len(relevant_sections)} relevante Zeilen gefunden")
# SEND_EMAIL: Orchestrator sendet Email an Team-Member
send_email_requests = re.findall(
r'@SEND_EMAIL\s*\nTo:\s*([^\n]+)\s*\nSubject:\s*([^\n]+)\s*\nBody:\s*([^@]+)@END',
response_text,
re.DOTALL
)
for to_addr, subject, body in send_email_requests:
to_clean = to_addr.strip()
subject_clean = subject.strip()
body_clean = body.strip()
# Versuche Email zu senden
success, message = send_email(to_clean, subject_clean, body_clean)
if success:
logger.info(f"[AgentCmd] Email gesendet an {to_clean}: {subject_clean}")
else:
logger.error(f"[AgentCmd] Email-Fehler: {message}")
# SEND_TELEGRAM: Orchestrator sendet Telegram-Nachricht
send_telegram_requests = re.findall(
r'@SEND_TELEGRAM\s*\nTo:\s*([^\n]+)\s*\nMessage:\s*([^@]+)@END',
response_text,
re.DOTALL
)
for recipient, message in send_telegram_requests:
recipient_clean = recipient.strip()
message_clean = message.strip()
# Telegram-Integration (wenn aktiviert)
if TELEGRAM_CONFIG.get('bot_token') and TELEGRAM_CONFIG.get('telegram_bot'):
try:
# Finde Chat-ID für Recipient (basierend auf Team-Member)
con = sqlite3.connect(EMAIL_JOURNAL_DB)
result = con.execute(
"SELECT telegram_chat_id FROM team_members WHERE name = ? OR email = ?",
(recipient_clean, recipient_clean)
).fetchone()
con.close()
if result and result[0]:
chat_id = result[0]
import asyncio
asyncio.run(TELEGRAM_CONFIG['telegram_bot'].bot.send_message(
chat_id=chat_id,
text=message_clean
))
logger.info(f"[AgentCmd] Telegram gesendet an {recipient_clean}")
else:
logger.warning(f"[AgentCmd] Keine Telegram Chat-ID für {recipient_clean}")
except Exception as e:
logger.error(f"[AgentCmd] Telegram-Fehler: {str(e)}")
else:
logger.warning("[AgentCmd] Telegram nicht konfiguriert")
# ADD_TEAM_MEMBER: Füge neues Team-Mitglied hinzu
add_member_requests = re.findall(
r'@ADD_TEAM_MEMBER\s*\nName:\s*([^\n]+)\s*\nEmail:\s*([^\n]+)\s*\nRole:\s*([^\n]+)\s*\nResponsibilities:\s*([^@]+)@END',
response_text,
re.DOTALL
)
for name, email, role, resp in add_member_requests:
name_clean = name.strip()
email_clean = email.strip()
role_clean = role.strip()
resp_clean = resp.strip()
success = add_team_member(name_clean, role_clean, email_clean, resp_clean)
if success:
logger.info(f"[AgentCmd] Team-Member hinzugefügt: {name_clean} ({role_clean})")
else:
logger.warning(f"[AgentCmd] Team-Member konnte nicht hinzugefügt werden: {name_clean}")
# UPDATE_TEAM_MEMBER: Aktualisiere Team-Mitglied
update_member_requests = re.findall(
r'@UPDATE_TEAM_MEMBER\s*\nEmail:\s*([^\n]+)\s*\nField:\s*([^\n]+)\s*\nValue:\s*([^@]+)@END',
response_text,
re.DOTALL
)
for email, field, value in update_member_requests:
email_clean = email.strip()
field_clean = field.strip().lower()
value_clean = value.strip()
# Hole aktuelles Team-Member
con = sqlite3.connect(EMAIL_JOURNAL_DB)
member = con.execute(
"SELECT name, role, email, responsibilities FROM team_members WHERE LOWER(email) = ?",
(email_clean.lower(),)
).fetchone()
if member:
# Update je nach Field
updates = {
'name': member[0],
'role': member[1],
'email': member[2],
'responsibilities': member[3]
}
if field_clean in updates:
updates[field_clean] = value_clean
success = update_team_member(
email_clean,
updates['name'],
updates['role'],
updates['responsibilities']
)
if success:
logger.info(f"[AgentCmd] Team-Member aktualisiert: {email_clean} - {field_clean}")
else:
logger.error(f"[AgentCmd] Update fehlgeschlagen für {email_clean}")
else:
logger.warning(f"[AgentCmd] Unbekanntes Field: {field_clean}")
else:
logger.warning(f"[AgentCmd] Team-Member nicht gefunden: {email_clean}")
con.close()
def create_new_agent(agent_key, role, skills):
"""Erstellt dynamisch einen neuen Agenten."""
agent_dir = os.path.join(AGENTS_BASE_DIR, agent_key)
@ -1448,15 +1342,24 @@ async def telegram_message_handler(update: Update, context: ContextTypes.DEFAULT
return
# Task erstellen
task_id = create_task(
title=f"Telegram: {message_text[:50]}{'...' if len(message_text) > 50 else ''}",
description=message_text,
agent_key='orchestrator',
task_type='telegram',
created_by=f'telegram_user_{user_id}',
telegram_chat_id=update.effective_chat.id,
telegram_user=username
)
task_id = len(tasks) + 1
while any(t['id'] == task_id for t in tasks):
task_id += 1
new_task = {
'id': task_id,
'title': f"Telegram: {message_text[:50]}{'...' if len(message_text) > 50 else ''}",
'description': message_text,
'assigned_agent': 'Orchestrator',
'agent_key': 'orchestrator',
'status': 'pending',
'created': datetime.now().strftime('%Y-%m-%d %H:%M'),
'type': 'telegram',
'created_by': f'telegram_user_{user_id}',
'telegram_chat_id': update.effective_chat.id,
'telegram_user': username
}
tasks.append(new_task)
await update.message.reply_text(
f"✅ Task #{task_id} erstellt!\n\n"
@ -1697,26 +1600,9 @@ Diese Email kommt von **{known_member[0]}** ({known_member[1]})
# Journal: Status 'queued' \Seen wird NICHT gesetzt (erst nach Verarbeitung)
journal_insert(message_id, email_id.decode(), sender, subject, 'queued', agent_key)
# Task erstellen in DB
task_id = create_task(
title=f"📧 Email: {subject[:50]}",
description=body[:500],
agent_key=agent_key,
task_type='email',
created_by=sender_email,
reply_to=sender_email,
reply_subject=f"Re: {subject}" if not subject.startswith('Re:') else subject,
original_sender=sender,
original_subject=subject,
original_body=body,
message_id=message_id,
imap_uid=email_id.decode(),
extra_context=extra_context
)
# Für task_queue brauchen wir Task als Dict (Legacy-Kompatibilität)
# Task erstellen und in beide Listen eintragen
task = {
'id': task_id,
'id': len(tasks) + 1,
'title': f"📧 Email: {subject[:50]}",
'description': body[:500],
'assigned_agent': AGENTS.get(agent_key, {}).get('name', agent_key),
@ -1729,11 +1615,11 @@ Diese Email kommt von **{known_member[0]}** ({known_member[1]})
'original_sender': sender,
'original_subject': subject,
'original_body': body,
'message_id': message_id,
'message_id': message_id, # für Journal-Update durch TaskWorker
'imap_uid': email_id.decode(),
'extra_context': extra_context,
'extra_context': extra_context, # Für unbekannte Absender
}
tasks.append(task)
with task_queue_lock:
task_queue.append(task)
@ -1741,7 +1627,7 @@ Diese Email kommt von **{known_member[0]}** ({known_member[1]})
log_entry['status'] = 'queued'
add_to_email_log(log_entry)
logger.info("[EmailPoller] Task #%d für Email von %s in Queue eingereiht.", task_id, sender_email)
logger.info("[EmailPoller] Task #%d für Email von %s in Queue eingereiht.", task['id'], sender_email)
except Exception as e:
logger.error("[EmailPoller] Fehler bei Email-ID %s: %s", email_id, str(e))
@ -1931,9 +1817,10 @@ Agent-Beschreibungen:
assigned = available_agents[0]
# Sub-Task mit KOMPLETTEM Kontext
sub_task_id = create_task(
title=t[:80],
description=f"""**Original-Aufgabe:**
sub_task = {
'id': len(tasks) + 1 + len(created_sub_tasks),
'title': t[:80],
'description': f"""**Original-Aufgabe:**
{task.get('title', '')}
{task.get('description', '')}
@ -1945,13 +1832,16 @@ Agent-Beschreibungen:
{t}
Arbeite diesen Teil ab und liefere ein vollständiges Ergebnis.""",
agent_key=assigned,
task_type='orchestrated',
created_by='orchestrator',
parent_task_id=task['id']
)
created_sub_tasks.append(sub_task_id)
logger.info("[TaskBeat] Sub-Task #%d zugewiesen an %s", sub_task_id, assigned)
'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
@ -2092,12 +1982,12 @@ Nutze @READ_KNOWLEDGE um Kontext zu holen falls nötig.
logger.info("[OrchestratorBeat] Frage Orchestrator zu %d Problemen", len(old_pending) + len(stuck_tasks))
response = execute_agent_task('orchestrator', summary)
# Response loggen und Kommandos parsen
# Response loggen
if response:
logger.info("[OrchestratorBeat] Orchestrator-Antwort: %s", response[:200])
# Parse Orchestrator-Kommandos (Email, Telegram, etc.)
parse_agent_commands('orchestrator', response)
# TODO: Parse @SEND_EMAIL und @SEND_TELEGRAM Kommandos
# und sende tatsächlich Benachrichtigungen
except Exception as e:
logger.error("[OrchestratorBeat] Fehler: %s", str(e))
@ -2118,9 +2008,7 @@ start_orchestrator_beat()
@app.route('/')
def index():
# Hole die 5 neuesten Tasks aus DB
all_tasks = get_tasks()
recent_tasks = all_tasks[:5] if all_tasks else []
recent_tasks = tasks[-5:] if tasks else []
return render_template('index.html', agents=AGENTS, recent_tasks=recent_tasks)
@app.route('/chat', methods=['GET', 'POST'])
@ -2201,30 +2089,28 @@ def task_list():
assigned_agent = request.form.get('assigned_agent', '')
if title:
task_id = create_task(
title=title,
description=description,
agent_key=assigned_agent if assigned_agent else None,
task_type='manual',
created_by='user'
)
flash(f'Task #{task_id} erstellt!', 'success')
task = {
'id': len(tasks) + 1,
'title': title,
'description': description,
'assigned_agent': AGENTS.get(assigned_agent, {}).get('name', assigned_agent) if assigned_agent else 'Nicht zugewiesen',
'status': 'pending',
'created': datetime.now().strftime('%Y-%m-%d %H:%M'),
'type': 'manual',
}
tasks.append(task)
flash('Task erstellt!', 'success')
# Alle Tasks aus Datenbank holen Neueste zuerst
all_tasks = get_tasks()
# Alle Tasks anzeigen Email-Tasks sind mit type='email' markiert
all_tasks = list(reversed(tasks)) # Neueste zuerst
return render_template('tasks.html', agents=AGENTS, tasks=all_tasks)
@app.route('/tasks/update/<int:task_id>/<status>')
def update_task(task_id, status):
# Update in Datenbank
update_task_db(task_id, status=status)
# Auch in-memory array aktualisieren (Legacy-Kompatibilität für task_queue)
for task in tasks:
if task['id'] == task_id:
task['status'] = status
break
return redirect(url_for('task_list'))
@app.route('/api/agent-stream', methods=['POST'])
@ -2812,35 +2698,26 @@ def api_tasks():
if not title:
return jsonify({'error': 'Kein Titel übergeben'}), 400
task_id = create_task(
title=title,
description=description,
agent_key=agent_key or assigned_agent or 'orchestrator',
task_type='agent_created',
created_by=agent_key or 'api'
)
# Task aus DB holen für Response
con = sqlite3.connect(EMAIL_JOURNAL_DB)
task_row = con.execute("SELECT * FROM tasks WHERE id = ?", (task_id,)).fetchone()
con.close()
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(agent_key or assigned_agent, {}).get('name', agent_key or assigned_agent) if (agent_key or assigned_agent) else 'Nicht zugewiesen',
'agent_key': agent_key or assigned_agent or 'orchestrator',
'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 or 'api'
'created_by': agent_key
}
tasks.append(new_task)
return jsonify({'success': True, 'task': new_task})
# GET: Alle Tasks aus DB
task_list = get_tasks()
task_list = list(reversed(tasks))
return jsonify({'tasks': task_list})
@ -2850,17 +2727,12 @@ def update_task_api(task_id):
data = request.get_json()
new_status = data.get('status')
# Update in DB
update_task_db(task_id, status=new_status)
# Auch in-memory aktualisieren (Legacy)
for task in tasks:
if task['id'] == task_id:
task['status'] = new_status
return jsonify({'success': True, 'task': task})
# Falls nur in DB vorhanden
return jsonify({'success': True, 'task_id': task_id, 'status': new_status})
return jsonify({'error': 'Task nicht gefunden'}), 404
@app.route('/api/models', methods=['GET'])
@ -2945,35 +2817,43 @@ def distribute_tasks():
tasks_text = '\n'.join([f"- {t}" for t in tasks_list])
agents_text = ', '.join(selected_agents) if selected_agents else 'alle verfügbaren Agenten'
planning_task_id = create_task(
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.",
agent_key='orchestrator',
task_type='orchestrated',
created_by='orchestrator',
sub_tasks=tasks_list,
available_agents=selected_agents
)
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)
return jsonify({
'success': True,
'message': f'Planungs-Task erstellt. Der Orchestrator wird die richtigen Agenten zuweisen.',
'tasks': [planning_task_id]
'tasks': [planning_task['id']]
})
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)
task_id = create_task(
title=task_text[:80],
description='Automatisch erstellt durch Orchestrator',
agent_key=agent_key,
task_type='orchestrated',
created_by='orchestrator'
)
# Setze Status sofort auf in_progress
update_task_db(task_id, status='in_progress')
created_tasks.append(task_id)
new_task = {
'id': len(tasks) + 1 + i,
'title': task_text[:80],
'description': 'Automatisch erstellt durch Orchestrator',
'assigned_agent': agent_name,
'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):