feat: Add Telegram bot integration and task detail/delete UI

- Wire up Telegram bot with token, allowed users and username from .env
- Fix TaskBeat to handle direct tasks (Telegram/email) without sub_tasks
- Fix send_telegram_message to use run_coroutine_threadsafe (avoid event loop clash)
- Add TaskBeat watchdog thread for auto-restart on crash
- Reset stuck in_progress tasks on startup
- Add task detail page (/tasks/<id>) with full response/log view and auto-refresh
- Add task delete route (/tasks/delete/<id>) with confirmation
- Include agent sender info in Telegram task prompts
- Orchestrator self-updated knowledge base with Telegram contact info
This commit is contained in:
eric 2026-02-21 18:14:43 +00:00
parent 5b4b698064
commit 99df910497
4 changed files with 237 additions and 34 deletions

117
templates/task_detail.html Normal file
View file

@ -0,0 +1,117 @@
{% extends "base.html" %}
{% block title %}Task #{{ task.id }}{% endblock %}
{% block content %}
<div class="page-header d-flex justify-content-between align-items-start flex-wrap gap-2">
<div>
<a href="{{ url_for('task_list') }}" style="color:var(--text-muted);font-size:.85rem;">&larr; Zurück zu Tasks</a>
<h1 class="mt-1">Task #{{ task.id }}</h1>
<p style="color:var(--text-muted);">{{ task.title }}</p>
</div>
<form method="POST" action="{{ url_for('delete_task', task_id=task.id) }}" onsubmit="return confirm('Task #{{ task.id }} wirklich löschen?')">
<button type="submit" class="btn btn-danger">Löschen</button>
</form>
</div>
<div class="row g-4">
<!-- Meta-Infos -->
<div class="col-12 col-md-4">
<div class="card h-100">
<div class="card-header bg-primary">
<h5 class="mb-0">Details</h5>
</div>
<div class="card-body">
<table class="table table-sm mb-0" style="font-size:.85rem;">
<tbody>
<tr><th style="color:var(--text-muted);white-space:nowrap;">Status</th><td>
{% if task.status == 'pending' %}
<span class="badge bg-warning">Pending</span>
{% elif task.status == 'in_progress' %}
<span class="badge bg-primary">In Progress</span>
{% elif task.status == 'completed' %}
<span class="badge bg-success">Done</span>
{% elif task.status == 'error' %}
<span class="badge bg-danger">Fehler</span>
{% else %}
<span class="badge bg-secondary">{{ task.status }}</span>
{% endif %}
</td></tr>
<tr><th style="color:var(--text-muted);">Typ</th><td><code>{{ task.type or '—' }}</code></td></tr>
<tr><th style="color:var(--text-muted);">Agent</th><td>{{ task.assigned_agent or task.agent_key or '—' }}</td></tr>
<tr><th style="color:var(--text-muted);">Erstellt von</th><td><code>{{ task.created_by or '—' }}</code></td></tr>
<tr><th style="color:var(--text-muted);">Erstellt</th><td>{{ task.created_at or '—' }}</td></tr>
<tr><th style="color:var(--text-muted);">Abgeschlossen</th><td>{{ task.completed_at or '—' }}</td></tr>
{% if task.telegram_chat_id %}
<tr><th style="color:var(--text-muted);">Telegram</th><td>{{ task.telegram_user or '' }} <code>{{ task.telegram_chat_id }}</code></td></tr>
{% endif %}
{% if task.parent_task_id %}
<tr><th style="color:var(--text-muted);">Parent Task</th><td><a href="{{ url_for('task_detail', task_id=task.parent_task_id) }}">#{{ task.parent_task_id }}</a></td></tr>
{% endif %}
{% if task.reply_to %}
<tr><th style="color:var(--text-muted);">Reply-To</th><td><code>{{ task.reply_to }}</code></td></tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Beschreibung + Antwort -->
<div class="col-12 col-md-8">
<div class="card mb-4">
<div class="card-header" style="background:var(--surface-2);">
<h5 class="mb-0">Anfrage / Beschreibung</h5>
</div>
<div class="card-body">
{% if task.description %}
<pre style="white-space:pre-wrap;font-family:inherit;font-size:.9rem;color:var(--text-primary);margin:0;">{{ task.description }}</pre>
{% else %}
<span style="color:var(--text-muted);">Keine Beschreibung.</span>
{% endif %}
</div>
</div>
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center" style="background:var(--surface-2);">
<h5 class="mb-0">Agent-Antwort / Log</h5>
{% if task.response %}
<button class="btn btn-sm btn-outline-secondary" onclick="copyResponse()">Kopieren</button>
{% endif %}
</div>
<div class="card-body p-0">
{% if task.status == 'in_progress' %}
<div class="p-3" style="color:var(--info);">
<span class="spinner-border spinner-border-sm me-2"></span>
Task läuft gerade... Seite wird alle 5s aktualisiert.
</div>
{% endif %}
{% if task.response %}
<pre id="responseBlock" style="white-space:pre-wrap;font-family:'JetBrains Mono',monospace;font-size:.82rem;color:var(--text-primary);margin:0;padding:1.25rem;max-height:600px;overflow-y:auto;">{{ task.response }}</pre>
{% elif task.status != 'in_progress' %}
<div class="p-3" style="color:var(--text-muted);">Noch keine Antwort vorhanden.</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
// Auto-Refresh wenn Task noch läuft
{% if task.status in ['pending', 'in_progress'] %}
setTimeout(() => location.reload(), 5000);
{% endif %}
function copyResponse() {
const text = document.getElementById('responseBlock').innerText;
navigator.clipboard.writeText(text).then(() => {
const btn = event.target;
btn.textContent = 'Kopiert!';
setTimeout(() => btn.textContent = 'Kopieren', 2000);
});
}
</script>
{% endblock %}

View file

@ -25,7 +25,7 @@
<th>Agent</th>
<th>Status</th>
<th>Erstellt</th>
<th>Aktion</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
@ -66,16 +66,11 @@
{% endif %}
</td>
<td style="color:var(--text-muted);font-size:.75rem;">{{ task.created }}</td>
<td>
{% if task.status == 'pending' %}
<span style="color:var(--text-muted);font-size:.75rem;">⏳ Wartend</span>
{% elif task.status == 'in_progress' %}
<span style="color:var(--info);font-size:.75rem;">🔄 Läuft...</span>
{% elif task.status == 'completed' %}
<span style="color:var(--success);font-size:.75rem;">✓ Auto</span>
{% else %}
<span style="color:var(--text-muted);font-size:.75rem;"></span>
{% endif %}
<td class="d-flex gap-2 align-items-center flex-wrap">
<a href="{{ url_for('task_detail', task_id=task.id) }}" class="btn btn-sm btn-outline-secondary" title="Details">Details</a>
<form method="POST" action="{{ url_for('delete_task', task_id=task.id) }}" onsubmit="return confirm('Task #{{ task.id }} wirklich löschen?')" style="display:inline;">
<button type="submit" class="btn btn-sm btn-outline-danger" title="Löschen">Löschen</button>
</form>
</td>
</tr>
{% endfor %}