feat: Enhanced Orchestrator Chat with notifications and better UX
- Add task creation notifications in Orchestrator Chat - New messages appear at top (newest first) - Add Clear Chat button with confirmation - Improve timestamps: DD.MM.YYYY HH:MM:SS format - Support for system notifications (task created, sub-agent responses) - Add add_orchestrator_notification() helper function - Auto-notify when agents create sub-tasks - Clear Chat route: POST /orchestrator/clear - Better visual distinction between messages and notifications
This commit is contained in:
parent
98ff812a82
commit
7ee66397e1
2 changed files with 94 additions and 13 deletions
44
app.py
44
app.py
|
|
@ -686,6 +686,25 @@ def get_tasks(status=None, limit=None):
|
||||||
return [dict(row) for row in rows]
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
|
def add_orchestrator_notification(message_type, message, agent_key=None):
|
||||||
|
"""Fügt eine System-Benachrichtigung zum Orchestrator-Chat hinzu."""
|
||||||
|
try:
|
||||||
|
if 'orchestrator_chat' not in session:
|
||||||
|
session['orchestrator_chat'] = []
|
||||||
|
|
||||||
|
session['orchestrator_chat'].insert(0, { # Insert at top (newest first)
|
||||||
|
'timestamp': datetime.now().strftime('%d.%m.%Y %H:%M:%S'),
|
||||||
|
'type': message_type,
|
||||||
|
'message': message,
|
||||||
|
'agent': AGENTS.get(agent_key, {}).get('name', agent_key) if agent_key else 'System',
|
||||||
|
'agent_key': agent_key,
|
||||||
|
'is_notification': True
|
||||||
|
})
|
||||||
|
session['orchestrator_chat'] = session['orchestrator_chat'][:50] # Keep last 50
|
||||||
|
session.modified = True
|
||||||
|
except:
|
||||||
|
pass # Session might not be available in background threads
|
||||||
|
|
||||||
def create_task(title, description, agent_key='orchestrator', task_type='manual', created_by='user', **kwargs):
|
def create_task(title, description, agent_key='orchestrator', task_type='manual', created_by='user', **kwargs):
|
||||||
"""Erstellt einen neuen Task in der Datenbank."""
|
"""Erstellt einen neuen Task in der Datenbank."""
|
||||||
con = sqlite3.connect(EMAIL_JOURNAL_DB)
|
con = sqlite3.connect(EMAIL_JOURNAL_DB)
|
||||||
|
|
@ -709,6 +728,15 @@ def create_task(title, description, agent_key='orchestrator', task_type='manual'
|
||||||
con.close()
|
con.close()
|
||||||
|
|
||||||
logging.info(f"[DB] Task #{task_id} erstellt: {title[:50]}")
|
logging.info(f"[DB] Task #{task_id} erstellt: {title[:50]}")
|
||||||
|
|
||||||
|
# Orchestrator-Notification hinzufügen
|
||||||
|
if created_by != 'user': # Nur bei Agent-erstellten Tasks
|
||||||
|
add_orchestrator_notification(
|
||||||
|
'task_created',
|
||||||
|
f'📋 Task #{task_id} erstellt: {title}',
|
||||||
|
agent_key=created_by
|
||||||
|
)
|
||||||
|
|
||||||
return task_id
|
return task_id
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2432,14 +2460,15 @@ def orchestrator():
|
||||||
response = execute_agent_task(selected_agent, prompt)
|
response = execute_agent_task(selected_agent, prompt)
|
||||||
|
|
||||||
orchestrator_chat = session.get('orchestrator_chat', [])
|
orchestrator_chat = session.get('orchestrator_chat', [])
|
||||||
orchestrator_chat.append({
|
orchestrator_chat.insert(0, { # Insert at top (newest first)
|
||||||
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M'),
|
'timestamp': datetime.now().strftime('%d.%m.%Y %H:%M:%S'),
|
||||||
'user_prompt': prompt,
|
'user_prompt': prompt,
|
||||||
'agent': agent_name,
|
'agent': agent_name,
|
||||||
'agent_key': selected_agent,
|
'agent_key': selected_agent,
|
||||||
'response': response
|
'response': response,
|
||||||
|
'is_notification': False
|
||||||
})
|
})
|
||||||
session['orchestrator_chat'] = orchestrator_chat[-30:]
|
session['orchestrator_chat'] = orchestrator_chat[:30] # Keep first 30
|
||||||
|
|
||||||
chat_display = session.get('orchestrator_chat', [])
|
chat_display = session.get('orchestrator_chat', [])
|
||||||
return render_template('orchestrator.html',
|
return render_template('orchestrator.html',
|
||||||
|
|
@ -2447,6 +2476,13 @@ def orchestrator():
|
||||||
chat_history=chat_display,
|
chat_history=chat_display,
|
||||||
knowledge_loaded=bool(session.get('orchestrator_kb')))
|
knowledge_loaded=bool(session.get('orchestrator_kb')))
|
||||||
|
|
||||||
|
@app.route('/orchestrator/clear', methods=['POST'])
|
||||||
|
def orchestrator_clear():
|
||||||
|
"""Löscht den Orchestrator Chat-Verlauf."""
|
||||||
|
session['orchestrator_chat'] = []
|
||||||
|
session.modified = True
|
||||||
|
return jsonify({'success': True})
|
||||||
|
|
||||||
@app.route('/agents', methods=['GET', 'POST'])
|
@app.route('/agents', methods=['GET', 'POST'])
|
||||||
def agents():
|
def agents():
|
||||||
agents_dir = os.path.join(os.path.dirname(__file__), 'agents')
|
agents_dir = os.path.join(os.path.dirname(__file__), 'agents')
|
||||||
|
|
|
||||||
|
|
@ -42,11 +42,26 @@
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header bg-dark d-flex justify-content-between align-items-center">
|
<div class="card-header bg-dark d-flex justify-content-between align-items-center">
|
||||||
<h5 class="mb-0">Orchestrator-Chat</h5>
|
<h5 class="mb-0">Orchestrator-Chat</h5>
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
<small style="color:var(--text-muted);">Automatische Agenten-Delegation</small>
|
<small style="color:var(--text-muted);">Automatische Agenten-Delegation</small>
|
||||||
|
{% if chat_history %}
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="clearOrchestratorChat()">🗑 Clear</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body chat-container" id="chatContainer">
|
<div class="card-body chat-container" id="chatContainer">
|
||||||
{% if chat_history %}
|
{% if chat_history %}
|
||||||
{% for chat in chat_history %}
|
{% for chat in chat_history %}
|
||||||
|
{% if chat.is_notification %}
|
||||||
|
<!-- System Notification -->
|
||||||
|
<div class="alert alert-info mb-2" style="font-size:0.875rem;padding:0.75rem;">
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;">
|
||||||
|
<span>{{ chat.message }}</span>
|
||||||
|
<small style="color:var(--text-muted);">{{ chat.timestamp }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<!-- Regular Chat Message -->
|
||||||
<div class="chat-message">
|
<div class="chat-message">
|
||||||
<div class="chat-timestamp">
|
<div class="chat-timestamp">
|
||||||
{{ chat.timestamp }} ·
|
{{ chat.timestamp }} ·
|
||||||
|
|
@ -55,6 +70,7 @@
|
||||||
<div class="chat-prompt mt-1"><strong>Sie:</strong> {{ chat.user_prompt }}</div>
|
<div class="chat-prompt mt-1"><strong>Sie:</strong> {{ chat.user_prompt }}</div>
|
||||||
<div class="chat-response mt-1"><strong>Orchestrator:</strong> {{ chat.response }}</div>
|
<div class="chat-response mt-1"><strong>Orchestrator:</strong> {{ chat.response }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-center py-5" style="color:var(--text-muted);" id="emptyState">
|
<div class="text-center py-5" style="color:var(--text-muted);" id="emptyState">
|
||||||
|
|
@ -89,7 +105,7 @@ function sendPromptWithStream() {
|
||||||
const msgDiv = document.createElement('div');
|
const msgDiv = document.createElement('div');
|
||||||
msgDiv.className = 'chat-message';
|
msgDiv.className = 'chat-message';
|
||||||
msgDiv.innerHTML = `
|
msgDiv.innerHTML = `
|
||||||
<div class="chat-timestamp">${new Date().toLocaleTimeString()} · <span class="badge bg-primary" style="font-size:.65rem;" id="agentBadge">wird ausgewählt…</span></div>
|
<div class="chat-timestamp">${formatTimestamp()} · <span class="badge bg-primary" style="font-size:.65rem;" id="agentBadge">wird ausgewählt…</span></div>
|
||||||
<div class="chat-prompt mt-1"><strong>Sie:</strong> ${escapeHtml(prompt)}</div>
|
<div class="chat-prompt mt-1"><strong>Sie:</strong> ${escapeHtml(prompt)}</div>
|
||||||
<div class="chat-response mt-1" id="responseDiv"><strong>Orchestrator:</strong> <span id="responseText">⏳ Agent arbeitet…</span></div>
|
<div class="chat-response mt-1" id="responseDiv"><strong>Orchestrator:</strong> <span id="responseText">⏳ Agent arbeitet…</span></div>
|
||||||
`;
|
`;
|
||||||
|
|
@ -186,5 +202,34 @@ function escapeHtml(text) {
|
||||||
div.textContent = text;
|
div.textContent = text;
|
||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearOrchestratorChat() {
|
||||||
|
if (!confirm('Möchten Sie den gesamten Chat-Verlauf löschen?')) return;
|
||||||
|
|
||||||
|
fetch('/orchestrator/clear', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
alert('Fehler beim Löschen: ' + err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTimestamp() {
|
||||||
|
const now = new Date();
|
||||||
|
const day = String(now.getDate()).padStart(2, '0');
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const hours = String(now.getHours()).padStart(2, '0');
|
||||||
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||||
|
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||||
|
return `${day}.${month}.${year} ${hours}:${minutes}:${seconds}`;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue