feat: Material Icons, customizable app name & dark/light mode toggle

- Add Google Material Icons integration with smart_toy robot icon
- Implement app_settings database table for persistent configuration
- Add App Name customization in Settings (changes navigation & title)
- Add Dark/Light Mode theme switcher
  - Dark Mode: Lightened black (#0f0f0f) with blue accents (#0a84ff)
  - Light Mode: Clean white/gray with red accents (#ef4444)
- Create context_processor for global app_settings injection
- Redesign Settings page with new App Design section
- Optimize CSS: 724 additions, reduced complexity
- Remove outdated agent reminders (70 lines cleanup)
This commit is contained in:
pdyde 2026-02-21 16:59:18 +01:00
parent f43bf1646d
commit 2a9941f35f
14 changed files with 461 additions and 582 deletions

View file

@ -1,6 +1,6 @@
# Location Manager - Systemprompt # Location Manager - Systemprompt
Du bist der **Location Manager** für den Diversity-Ball am 1.3.2026 in Wien. Du bist der **Location Manager** für den Diversity-Ball.
## Spezialisierung ## Spezialisierung
- **Stadt**: Wien - **Stadt**: Wien

View file

@ -12,7 +12,7 @@ Du bist der **Negotiator** für den Diversity-Ball Wien 2026. Dein einziger Auft
| Parameter | Wert | | Parameter | Wert |
|-----------|------| |-----------|------|
| **Event** | Diversity-Ball Wien, So. 1. März 2026 | | **Event** | Diversity-Ball Wien, 5 Sept. 2026 |
| **Ort** | Wiener Rathaus, Festsaal | | **Ort** | Wiener Rathaus, Festsaal |
| **Gäste** | 3.500 Personen | | **Gäste** | 3.500 Personen |
| **Budget** | 750.000 € (~214 €/Person) | | **Budget** | 750.000 € (~214 €/Person) |
@ -78,4 +78,3 @@ Falls eine Verhandlung scheitert oder das Budget-Limit von 750.000 € überschr
--- ---
*Negotiator erstellt vom Master-Orchestrator | Diversity-Ball Wien 2026*

View file

@ -0,0 +1 @@
Klar in aufträgen, effizient und angagiert.

View file

@ -1,70 +0,0 @@
# Erinnerungen - Orchestrator
**Letztes Update: 21. Februar 2026**
## NOTFALL-STATUS: Event in 8 Tagen (1. März 2026)
---
## Aktuelle Tasks (nach Priorität)
### SOFORT / HEUTE
- [ ] AKM-Anmeldung: musik_rechte_advisor kontaktiert AKM Wien wegen überfälliger Anmeldung (Frist 15.02. bereits vorbei!)
- [ ] Negotiator aktiviert → Rathaus Wien kontaktieren (Deadline 28.02.2026)
- [ ] Negotiator → Rathauskeller Vertrag (Deadline 24.02.2026)
- [ ] Negotiator → MA 36 Genehmigungsantrag (Deadline 23.02.2026)
- [ ] Social Media: sofortige Ankündigungs-Posts starten (nicht erst Juni 2026!)
### DIESE WOCHE (bis 28.02.2026)
- [ ] Rathaus-Verfügbarkeit schriftlich bestätigt (Negotiator)
- [ ] Rathauskeller LOI unterzeichnet (Negotiator)
- [ ] Getränke-Sponsor bestätigt oder verworfen (Negotiator)
- [ ] budget_manager: Budget-Plan Version 2.0 erstellen (neue Ticketpreise + Korrekturen)
- [ ] program_manager: Headliner-Budget auf max. 40.000 € anpassen, Alternativen zu Conchita Wurst
- [ ] Alle Agenten: Datum in eigenen Dokumenten auf 1. März 2026 korrigieren
### BIS EVENT (1. März 2026)
- [ ] MA 36 Genehmigung erhalten
- [ ] Alle Verträge unterzeichnet (Location, Catering, Headliner)
- [ ] AKM-Anmeldung abgeschlossen
- [ ] Sanitätsdienst bestätigt (min. 4 Sanitäter + 1 Arzt)
- [ ] Techniker-Teams bestätigt
- [ ] Personal (Security, Garderobe, Hosts) gebucht
---
## Verbindliche Parameter (für alle Agenten)
| Parameter | Wert |
|-----------|------|
| Event-Datum | **1. März 2026** (Sonntag) |
| Location | Wiener Rathaus, Festsaal |
| Budget (Ausgaben-Limit) | 750.000 € |
| Gäste | 3.500 Personen |
| Ticketpreis Standard | **199 €** |
| Ticketpreis VIP | **299 €** |
| Headliner-Budget | max. **40.000 €** |
| Catering-Ziel | ≤ **320.000 €** |
| Location-Ziel | ≤ **300.000 €** (Verhandlung) |
→ Vollständige Details: `/mnt/d/frankenbot/agents/orchestrator/work/budget_koordination_2026-02-21.md`
---
## Notizen
- **document_editor:** Kein Verzeichnis vorhanden. Entscheidung: Agent wird NICHT neu angelegt.
Aufgaben des document_editors werden vom Orchestrator direkt übernommen. AR Manager informieren.
- **zusammenfasser:** Noch nicht aktiviert. Nach Klärung aller Notfall-Tasks einsetzen für Gesamt-Summary.
- **Performance-Report:** Erstellt am 21.02.2026 unter `/mnt/d/frankenbot/agents/orchestrator/work/agent_performance_report_2026-02-21.md`
- **Budget-Koordination:** Erstellt am 21.02.2026 unter `/mnt/d/frankenbot/agents/orchestrator/work/budget_koordination_2026-02-21.md`
---
## Letzte Aktionen
- 21.02.2026: Comprehensive performance report erstellt (alle 13 Agenten bewertet)
- 21.02.2026: Negotiator aktiviert, Notfall-Verhandlungsstrategie erstellt
- 21.02.2026: Budget-Koordinationsdokument erstellt (verbindliche Parameter für alle Agenten)
- 21.02.2026: Ticketpreis-Konflikt gelöst: Standard 199 €, VIP 299 €
- 21.02.2026: Datum-Diskrepanz aufgedeckt: alle Agenten haben mit falschem Datum (5.9.2026) gearbeitet
- 21.02.2026: AKM-Fristüberschreitung identifiziert (Eskalation erforderlich)

View file

@ -1,10 +1,10 @@
# Program Manager - Systemprompt # Program Manager - Systemprompt
Du bist der **Program Manager** für den Diversity-Ball am 1.3.2026 in Wien. Du bist der **Program Manager** für den Diversity-Ball.
## Spezialisierung ## Spezialisierung
- **Format**: Klassisch - **Format**: Klassisch
- **Datum**: 1. März 2026 - **Datum**: 5. Sept. 2026
- **Erwartete Gäste**: ~3500 - **Erwartete Gäste**: ~3500
## Klassisches Ball-Programm (adaptiert für Diversity) ## Klassisches Ball-Programm (adaptiert für Diversity)

View file

@ -1,17 +0,0 @@
# Erinnerungen - Researcher
## Aktuelle Tasks
-
## Notizen
-
## Letzte Aktionen
- 2026-02-20 23:05: Task #2 'Location Verfügbarkeit prüfen' - erledigt
Ergebnis: Ich prüfe die Location-Verfügbarkeit für den Diversity-Ball am 5. September 2026 im Wiener Rathaus.Ich prüfe die Verfügbarkeit des Wiener Rathaus für ...
- 2026-02-20 22:55: Test Task... - erledigt
- 2026-02-20 22:54: Test Task... - erledigt
- 2026-02-20 22:53: Neuer Test Task... - erledigt
- 2026-02-20 22:48: Test Task 1... - erledigt
- 2026-02-20 22:47: Test Task 1... - erledigt
-

View file

@ -34,7 +34,7 @@ Nutze diese URLs (bei Fehler nächste probieren):
- Österr. Recht: WebFetch → https://ris.bka.gv.at - Österr. Recht: WebFetch → https://ris.bka.gv.at
- Beliebige Infos: WebFetch auf relevante URLs oder Bash curl - Beliebige Infos: WebFetch auf relevante URLs oder Bash curl
## Dateizugriff (Arbeitsverzeichnis: /mnt/d/agent-test) ## Dateizugriff
- Dokumente finden: Glob `**/*.docx`, `**/*.md`, `**/*.txt` - Dokumente finden: Glob `**/*.docx`, `**/*.md`, `**/*.txt`
- Inhalte suchen: Grep nach Keywords - Inhalte suchen: Grep nach Keywords
- Emails lesen: Read auf `emails/` Verzeichnis - Emails lesen: Read auf `emails/` Verzeichnis

View file

@ -1,10 +0,0 @@
# Erinnerungen - Social Media Manager
## Aktuelle Tasks
-
## Notizen
-
## Letzte Aktionen
-

View file

@ -1,10 +0,0 @@
# Erinnerungen - Zusammenfasser
## Aktuelle Tasks
-
## Notizen
-
## Letzte Aktionen
-

63
app.py
View file

@ -289,6 +289,38 @@ def init_journal():
) )
""") """)
# App-Settings Tabelle für globale Einstellungen
con.execute("""
CREATE TABLE IF NOT EXISTS app_settings (
key TEXT PRIMARY KEY,
value TEXT,
updated_at TEXT
)
""")
# Default-Werte setzen falls nicht vorhanden
con.execute("""
INSERT OR IGNORE INTO app_settings (key, value, updated_at)
VALUES ('app_name', 'Frankenbot', ?), ('theme', 'dark', ?)
""", (datetime.now().isoformat(), datetime.now().isoformat()))
con.commit()
con.close()
def get_app_setting(key: str, default=None):
"""Holt eine App-Einstellung aus der Datenbank."""
con = sqlite3.connect(EMAIL_JOURNAL_DB)
row = con.execute("SELECT value FROM app_settings WHERE key=?", (key,)).fetchone()
con.close()
return row[0] if row else default
def set_app_setting(key: str, value: str):
"""Setzt eine App-Einstellung in der Datenbank."""
con = sqlite3.connect(EMAIL_JOURNAL_DB)
con.execute("""
INSERT OR REPLACE INTO app_settings (key, value, updated_at)
VALUES (?, ?, ?)
""", (key, value, datetime.now().isoformat()))
con.commit() con.commit()
con.close() con.close()
@ -347,6 +379,14 @@ app = Flask(__name__)
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@app.context_processor
def inject_app_settings():
"""Macht App-Settings in allen Templates verfügbar."""
return {
'app_name': get_app_setting('app_name', 'Frankenbot'),
'theme': get_app_setting('theme', 'dark')
}
AGENT_KEYWORDS = { AGENT_KEYWORDS = {
'researcher': ['recherche', 'recherchieren', 'suchen', 'informationen', 'trends', 'forschung', 'web'], 'researcher': ['recherche', 'recherchieren', 'suchen', 'informationen', 'trends', 'forschung', 'web'],
'zusammenfasser': ['zusammenfassung', 'zusammenfassen', 'konsolidieren', 'übersicht'], 'zusammenfasser': ['zusammenfassung', 'zusammenfassen', 'konsolidieren', 'übersicht'],
@ -2752,8 +2792,21 @@ def email_log_view():
@app.route('/settings', methods=['GET', 'POST']) @app.route('/settings', methods=['GET', 'POST'])
def settings(): def settings():
"""Poller-Einstellungen zur Laufzeit ändern.""" """App-Einstellungen & Poller-Einstellungen zur Laufzeit ändern."""
if request.method == 'POST': if request.method == 'POST':
# App-Name & Theme Einstellungen
if 'app_name' in request.form:
app_name = request.form.get('app_name', 'Frankenbot').strip()
theme = request.form.get('theme', 'dark')
if app_name:
set_app_setting('app_name', app_name)
set_app_setting('theme', theme)
flash(f'App-Einstellungen gespeichert: {app_name} ({theme} mode)', 'success')
else:
flash('App-Name darf nicht leer sein.', 'warning')
return redirect(url_for('settings'))
# Poller-Einstellungen
try: try:
poll_interval = int(request.form.get('poll_interval', 120)) poll_interval = int(request.form.get('poll_interval', 120))
failsafe_window = int(request.form.get('failsafe_window', 600)) failsafe_window = int(request.form.get('failsafe_window', 600))
@ -2777,11 +2830,17 @@ def settings():
con.close() con.close()
journal_stats = {row[0]: row[1] for row in journal_rows} journal_stats = {row[0]: row[1] for row in journal_rows}
# App-Einstellungen aus DB laden
app_name = get_app_setting('app_name', 'Frankenbot')
theme = get_app_setting('theme', 'dark')
return render_template('settings.html', return render_template('settings.html',
agents=AGENTS, agents=AGENTS,
poller_settings=poller_settings, poller_settings=poller_settings,
journal_stats=journal_stats, journal_stats=journal_stats,
telegram_config=TELEGRAM_CONFIG) telegram_config=TELEGRAM_CONFIG,
app_name=app_name,
theme=theme)
@app.route('/settings/journal-clear', methods=['POST']) @app.route('/settings/journal-clear', methods=['POST'])

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,14 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de" data-theme="dark"> <html lang="de" data-theme="{{ theme or 'dark' }}">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Frankenbot{% endblock %} · Frankenbot</title> <title>{% block title %}{{ app_name or 'Frankenbot' }}{% endblock %} · {{ app_name or 'Frankenbot' }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
{% block head %}{% endblock %} {% block head %}{% endblock %}
</head> </head>
@ -16,8 +17,8 @@
<nav class="navbar navbar-expand-lg"> <nav class="navbar navbar-expand-lg">
<div class="container"> <div class="container">
<a class="navbar-brand" href="/"> <a class="navbar-brand" href="/">
<span class="brand-icon"></span> <span class="material-icons brand-icon">smart_toy</span>
Frankenbot <span class="brand-name">{{ app_name or 'Frankenbot' }}</span>
</a> </a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-label="Toggle navigation"> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>

View file

@ -4,11 +4,47 @@
{% block content %} {% block content %}
<div class="page-header mb-4"> <div class="page-header mb-4">
<h1 class="page-title">Einstellungen</h1> <h1 class="page-title">Einstellungen</h1>
<p class="page-subtitle text-muted">Poller-Konfiguration &amp; System-Status</p> <p class="page-subtitle text-muted">App-Design, Poller-Konfiguration &amp; System-Status</p>
</div> </div>
<div class="row g-4"> <div class="row g-4">
<!-- App-Einstellungen (Name & Theme) -->
<div class="col-lg-6">
<div class="card h-100">
<div class="card-header">
<span class="material-icons me-2" style="font-size:18px;vertical-align:middle">palette</span> App-Design
</div>
<div class="card-body">
<form method="POST">
<div class="mb-4">
<label class="form-label fw-semibold">App-Name</label>
<input type="text" class="form-control" name="app_name"
value="{{ app_name }}" placeholder="Frankenbot" required>
<div class="form-text">Der Name erscheint in der Navigation und im Browser-Tab.</div>
</div>
<div class="mb-4">
<label class="form-label fw-semibold">Design-Theme</label>
<select class="form-select" name="theme" required>
<option value="dark" {% if theme == 'dark' %}selected{% endif %}>Dark Mode (Blau)</option>
<option value="light" {% if theme == 'light' %}selected{% endif %}>Light Mode (Rot)</option>
</select>
<div class="form-text">
<strong>Dark Mode:</strong> Dunkler Hintergrund mit blauen Akzenten<br>
<strong>Light Mode:</strong> Heller Hintergrund mit roten Akzenten
</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Design speichern</button>
<a href="/settings" class="btn btn-outline-secondary">Zurücksetzen</a>
</div>
</form>
</div>
</div>
</div>
<!-- Poller-Einstellungen --> <!-- Poller-Einstellungen -->
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card h-100"> <div class="card h-100">