feat: initial commit – Frankenbot Multi-Agent Orchestration System

- Flask Web-App mit Dashboard, Chat, Orchestrator, Tasks, Dateien, Emails, Agenten, Settings
- Email-Poller (IMAP) mit SQLite-Journal als Failsafe (kein Emailverlust bei Absturz)
- Failsafe-Fenster und Poll-Intervall zur Laufzeit via /settings konfigurierbar
- TaskWorker: IMAP Seen-Flag erst nach erfolgreichem Task-Abschluss
- Whitelist-Filter: eric.fischer, p.dyderski, georg.tschare (gmail + signtime.media), *@diversityball.at
- 9 Agenten: researcher, tax_advisor, document_editor, location_manager, program_manager,
  catering_manager, musik_rechte_advisor, zusammenfasser, orchestration_ui
- Diversity Ball Wien 2026 – Wissensdatenbank, Sponsoringverträge, Email-Vorlagen
This commit is contained in:
Pjot 2026-02-20 17:31:16 +01:00
commit 56d9bc2c76
71 changed files with 5953 additions and 0 deletions

20
.env.example Normal file
View file

@ -0,0 +1,20 @@
# Email Integration Configuration
# Gmail Example:
# 1. Enable 2-Factor Authentication on your Google Account
# 2. Generate an App-Specific Password: https://myaccount.google.com/apppasswords
# 3. Copy the 16-character password below
IMAP_SERVER=imap.gmail.com
SMTP_SERVER=smtp.gmail.com
EMAIL_ADDRESS=your-email@gmail.com
EMAIL_PASSWORD=your-app-specific-password
IMAP_PORT=993
SMTP_PORT=587
# Other common providers:
# Outlook/Office365:
# IMAP: imap-mail.outlook.com (Port 993)
# SMTP: smtp-mail.outlook.com (Port 587)
# Yahoo:
# IMAP: imap.mail.yahoo.com (Port 993)
# SMTP: smtp.mail.yahoo.com (Port 587)

31
.gitignore vendored Normal file
View file

@ -0,0 +1,31 @@
# Secrets & Konfiguration
.env
# Datenbank (enthält Email-IDs und Status kein Source-Control-Artefakt)
email_journal.db
# Python
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
*.egg-info/
dist/
build/
.venv/
venv/
env/
# Uploads (user-generated content)
uploads/
# Logs
*.log
/tmp/
# IDE
.vscode/
.idea/
*.swp
*.swo

View file

@ -0,0 +1,165 @@
# AKM-Deklaration für Diversity-Ball Wien
**Veranstaltung:** Diversity-Ball Wien
**Datum:** 5. September 2026
**Ort:** Wiener Rathaus
**Veranstalter:** [Veranstalter Name/Firma]
**Erwartete Gäste:** 3.500
**Programmzeit:** 18:00 - 02:00 Uhr (8 Stunden)
---
## 1. Tarifanwendung
### Maßgeblicher Tarif
- **Autonomer Tarif für Einzelveranstaltungen (AT-EV)**
- Fassungsvermögen Wiener Rathaus (Festsaal + Zusatzräume): ca. 3.500 Personen
- Es liegt **Publikumstanz** vor (Tanz ab 21:30 Uhr)
### Berechnungsart
Der Veranstalter kann zwischen zwei Abrechnungsarten wählen:
| Abrechnungsart | Formel | Anwendung |
|----------------|--------|-----------|
| **Pauschale** | Fassungsvermögen × Stundensatz | Bei großen Veranstaltungen (>500 Pers.) meist günstiger |
| **Prozent** | 14% der Brutto-Einnahmen | Bei hohem Kartenpreis günstiger |
---
## 2. Programmzuordnung Musiknutzung
| Uhrzeit | Programmpunkt | Musikart | Tarifrelevant |
|---------|---------------|----------|---------------|
| 18:00-19:00 | Einlass & Sektempfang | Hintergrundmusik | ✓ Tanzveranstaltung |
| 19:00-20:00 | Eröffnung & Reden | Redemusik/keine Musik | ggf. separate Meldung |
| 20:00-21:30 | Festliches Menü | Hintergrundmusik | ✓ Tanzveranstaltung |
| 21:30-22:15 | Unterhaltung / Tanz | Tanzmusik (DJ/Live) | ✓ Tanzveranstaltung |
| 22:15-23:30 | Diversity-Awards | Moderation + Musik | ✓ Tanzveranstaltung |
| 23:30-01:30 | Tombola / Versteigerung | Unterhaltungsmusik | ✓ Tanzveranstaltung |
| 01:30-02:00 | Abschlussreden | Redemusik | ggf. separate Meldung |
**Hinweis:** Die gesamte Veranstaltung gilt als "Tanzveranstaltung", da Publikumstanz stattfindet.
---
## 3. Ermäßigungen
### 3.1 IG Kultur Österreich Ermäßigung (40%)
- **Voraussetzung:** Der Veranstalter ist Mitglied der IG Kultur Österreich oder eines angeschlossenen Landesverbandes
- **Nachweis:** Aktueller Mitgliedsnachweis bei Anmeldung vorlegen
- **Feld bei Anmeldung:** "Dach-/Fachverband" → "IG Kultur"
### 3.2 Gemeinnützige/Benefiz-Ermäßigung
- Der Diversity-Ball ist eine **Benefizveranstaltung** (Erlös geht an gemeinnützigen Zweck)
- **Nachweis erforderlich:**
- Vereinsstatuten (Nachweis der Gemeinnützigkeit lt. § 34 ff BAO)
-/Finanzamt-Bescheid über Gemeinnützigkeit
- Angabe des Begünstigten (welcher Zweck wird gefördert?)
### 3.3 Kein Anspruch auf Ermäßigung
Ermäßigungen gelten NICHT bei:
- Kooperation mit nicht-begünstigten Veranstaltern
- Veranstaltungen mit gewerblicher Bewirtung (wenn AKM-Pauschale günstiger)
---
## 4. Kostenberechnung (Schätzung)
### Aktuelle Tarife (gültig ab 01.11.2024)
Die genauen Tarife sind im "Autonomen Tarif für Einzelveranstaltungen (AT-EV)" geregelt.
Nächste Tarifanpassung voraussichtlich erst ab 01.11.2025.
### Variante A: Pauschalabrechnung (empfohlen)
- Fassungsvermögen: 3.500 Personen
- Die Pauschalberechnung richtet sich nach:
- Fassungsvermögen (in 100er-Schritten)
- Veranstaltungsdauer (Stundensatz)
- Musikart (Tanz/kein Tanz)
- ** Für eine grobe Schätzung bei 3.500 Pers./8 Std./Tanz: ca. € 5.000-6.000**
- + AUME/LSG-Zuschläge (ca. 15%): ca. € 750-900
- **Gesamtschätzung (ohne Ermäßigung): ca. € 5.750-6.900**
### Variante B: Prozentabrechnung
- Angenommene Einnahmen: 3.500 Karten × € 120 = € 420.000
- Tarif: **14%** (bei Tanzveranstaltung)
- **Berechnung:** € 420.000 × 14% = **€ 58.800,00**
- *Hinweis: Prozentabrechnung ist bei dieser Veranstaltung wegen des hohen Kartenpreises NICHT sinnvoll*
### Mit 40% Ermäßigung (IG Kultur):
- Pauschale: ~€ 5.500 × 60% = **~€ 3.300**
- + Zuschläge: ~€ 800 × 60% = **~€ 480**
- **Gesamtschätzung: ca. € 3.780**
> **WICHTIG:** Dies ist eine Schätzung. Die genauen Kosten werden von der AKM nach Anmeldung berechnet. Empfehlung: Vor der Veranstaltung ein Angebot bei der AKM anfordern.
---
## 5. Erforderliche Nachweise für Ermäßigung
### Für IG Kultur-Mitglieder:
- [ ] Aktueller Mitgliedsausweis (Kopie)
- [ ] Anmeldung unter "IG Kultur" als Dachverband
### Für gemeinnützige Veranstalter:
- [ ] Aktueller Vereinsregisterauszug
- [ ] Statuten (mit Gemeinnützigkeitsnachweis)
- [ ] Finanzamt-Bestätigung der Gemeinnützigkeit
- [ ] Nachweis über Verwendung des Erlöses (Begünstigten-Nachweis)
---
## 6. Einreichung bei AKM
### Online-Anmeldung
1. Registrierung/ Login unter: https://www.akm.at/musiknutzende/
2. "Einzelveranstaltung anmelden"
3. Folgende Daten eingeben:
- Veranstaltungsdatum: 05.09.2026
- Veranstaltungsort: Wiener Rathaus, Lichtenfelsgasse 2, 1010 Wien
- Veranstaltungsart: Benefizball / Tanzveranstaltung
- Fassungsvermögen: 3.500
- Dauer: 8 Stunden (18:00-02:00 Uhr)
- Musiknutzung: mechanische Musik + Live-Musik
- Dachverband: IG Kultur (falls zutreffend)
### Programm-Meldung (innerhalb 10 Tage nach Veranstaltung)
- Auflistung der gespielten Werke via AKM-Portal
- Alternativ: pauschales Sammelprogramm bei gleichbleibendem Repertoire
### Fristen
- Anmeldung: mind. 3 Werktage vor Veranstaltung
- Programm-Meldung: innerhalb 10 Tage nach Veranstaltung
- Zahlung: 4 Wochen nach Rechnungslegung
---
## 7. Wichtige Hinweise
⚠️ **Strafzahlungen vermeiden:**
- Bei verspäteter Anmeldung oder fehlender Programm-Meldung: **doppelter Tarif**
- Anspruch auf Ermäßigung geht verloren
💡 **Empfehlung:**
- Vor Buchung Kontakt mit AKM (Tel.: +43 1 505 62 62) oder IG Kultur
- Angebots-Einholung für exakte Kalkulation
- Bei Unklarheiten: schriftliche Vorabstimmung mit AKM
---
## 8. Ansprechpartner
**AKM Wien**
- Adresse: Baumannstraße 10, 1030 Wien
- Tel.: +43 1 505 62 62
- E-Mail: veranstaltung@akm.at
- Online: www.akm.at/musiknutzende/
**IG Kultur Österreich**
- Adresse: Gumpendorfer Straße 63, 1060 Wien
- Tel.: +43 1 503 23 20
- E-Mail: office@igkultur.at
---
*Erstellt am: [Datum]*
*Dieses Dokument dient als Vorlage. Die tatsächlichen Kosten können abweichen.*

192
CHANGES.md Normal file
View file

@ -0,0 +1,192 @@
# Implementierte Änderungen - Features Summary
## 📊 Übersicht der Änderungen
### Feature 1: Streaming-UI für Agent-Responses
**Datei: app.py**
- **Zeile 10**: `Response` zu Imports hinzugefügt
- **Zeile 67**: Timeout von 120 auf 300 Sekunden erhöht
- **Zeilen 250-288**: Neue SSE-Route `/api/agent-stream` implementiert
- JSON-basierte Events: agent_selected, processing, response_chunk, complete, error
- Chunk-basiertes Streaming (50 Zeichen pro Event)
- Error-Handling mit try-except
**Datei: templates/orchestrator.html**
- **Zeilen 66-74**: "Live-Antwort anfordern" Button hinzugefügt
- **Zeilen 75-76**: "Klassisch senden" Button für alte Methode
- **Zeilen 185-232**: JavaScript Streaming-Handler implementiert
- `sendPromptWithStream()` Funktion
- Fetch API mit ReadableStream Reader
- SSE Event-Parser für JSON-Objekte
- Live DOM-Updates für Chat-Nachrichten
---
### Feature 2: Email-Integration (IMAP/SMTP)
**Datei: app.py**
- **Zeilen 5-9**: Email-Imports hinzugefügt
- `imaplib` für IMAP-Verbindungen
- `smtplib` und `email.mime` für SMTP-Versand
- **Zeilen 29-36**: Email-Konfiguration mit Umgebungsvariablen
- Standard Gmail-Einstellungen
- Fallback auf Environment-Variablen
- **Zeilen 194-221**: `get_emails()` Funktion
- IMAP SSL-Verbindung
- Liest letzte 10 Emails
- Extrahiert Subject, From, Date, Preview
- **Zeilen 224-232**: `get_email_preview()` Funktion
- Extrahiert Text-Preview aus Email
- Multipart-Handling
- UTF-8 Decoding mit Fallback
- **Zeilen 235-259**: `get_email_body()` Funktion
- Vollständiger Email-Body Abruf
- Lazy Loading (on-demand)
- Error-Handling mit Fehlermeldungen
- **Zeilen 262-284**: `send_email()` Funktion
- SMTP-Versand mit STARTTLS
- MIMEMultipart für strukturierte Emails
- Login + Send + Quit Workflow
- Rückgabe von Success-Flag + Nachricht
- **Zeilen 483-509**: Route `/emails` (GET/POST)
- Email-Management Dashboard rendern
- Email-Versand-Form verarbeiten
- Config-Status Display
- **Zeilen 512-520**: Route `/emails/<email_id>` (GET)
- JSON-Response mit Email-Body
- Config-Validierung
**Datei: templates/emails.html** (Neue Datei)
- Bootstrap 5 basiertes Layout
- Zwei-spaltige UI:
- **Links**: Email-Verfassung Form (To, Subject, Body, Send)
- **Rechts**: Posteingang mit Email-Liste
- Modal-Dialog für Email-Detail-View
- JavaScript `viewEmail()` für Lazy-Loading
- Responsive Design
**Datei: .env.example** (Neue Datei)
- Gmail-Setup Anleitung (mit 2FA)
- Outlook/Office365 Config
- Yahoo Config
- Kommentierte Konfigurationsoptionen
**Navigation Updates** in allen HTML-Templates:
- `templates/index.html`
- `templates/chat.html`
- `templates/tasks.html`
- `templates/files.html`
- `templates/agents.html`
- `templates/orchestrator.html`
Jede Datei erhielt den neuen Navbar-Item:
```html
<li class="nav-item">
<a class="nav-link" href="/emails">📧 Emails</a>
</li>
```
---
## 📈 Statistiken
| Metrik | Wert |
|--------|------|
| Neue Python-Funktionen | 4 |
| Neue Routes | 2 |
| Neue HTML-Templates | 1 |
| Neue Config-Dateien | 1 |
| Template-Updates | 6 |
| Zeilen Code hinzugefügt | ~350 |
| Timeout-Erhöhung | 120s → 300s |
---
## 🔄 Flow-Diagramme
### Streaming-Flow
```
User schreibt Prompt
Klick "Live-Antwort anfordern"
POST /api/agent-stream (JSON)
Server: SSE Generator starten
Event 1: agent_selected 🤖
Event 2: processing ⏳
Event 3: response_chunk (x multiple)
Event 4: complete ✓
Browser: Live-Update Chat-Container
Modal zeigt: "⏳ Agent arbeitet..." → Antwort
```
### Email-Send-Flow
```
User füllt Form aus
POST /emails (to, subject, body)
send_email(to, subject, body)
SMTP connect → login → send_message → quit
Response: Success/Error Message
Flash message anzeigen
```
### Email-Receive-Flow
```
GET /emails
get_emails() → IMAP SSL connect
Select INBOX → Search ALL
Fetch letzte 10 Email-IDs
Für jede Email: extract Subject, From, Date, Preview
Close IMAP connection
Template: Render Email-Liste
User klick auf Email
fetch(/emails/<id>) → get_email_body()
Modal zeigt: Full Body Content
```
---
## ✨ Highlights
### Streaming
- Non-blocking Stream Reader für echte Echtzeit
- Chunks für bessere UX (nicht ganze Antwort auf einmal)
- Status-Updates während Verarbeitung
- 5-minütiger Timeout (300s) für lange Anfragen
### Email
- Zero-Abhängigkeiten (nur Python Standard Library)
- Sichere Credential-Handling (env vars only)
- Multipart Email-Support
- UTF-8 Encoding mit Fallback
---
## 🚀 Nächste Schritte (Optional)
- [ ] Email-Datei-Anhänge (Attachments)
- [ ] Email-Suche & Filter
- [ ] HTML-Email-Rendering
- [ ] Automatische Email-Klassifizierung (Agents)
- [ ] Streaming mit WebSockets statt SSE
- [ ] Email-Caching (lokale DB)
- [ ] Multi-Account Support

213
FEATURES.md Normal file
View file

@ -0,0 +1,213 @@
# Neue Features - Streaming UI & Email Integration
## 1. 🎬 Streaming-UI für Agent-Responses
### Problem
- Lange Wartezeiten bei Agent-Anfragen ohne Feedback
- Nutzer wusste nicht, ob das System noch aktiv ist
### Lösung
Eine neue **Server-Sent Events (SSE)** basierte Streaming-UI zeigt live Feedback während der Agent-Verarbeitung.
### Features
✨ **Live-Updates während Verarbeitung**
- "⏳ Agent arbeitet..." - Status während Verarbeitung
- "🤖 Aktiver Agent: [Name]" - Angezeigter delegierter Agent
- Schrittweise Antwort-Chunks streaming zum Browser
- "✓ Verarbeitung abgeschlossen" - Erfolgsbestätigung
### Technische Details
- **Timeout erhöht** auf 300 Sekunden (war: 120s)
- **Neue Route**: `/api/agent-stream` (POST, JSON)
- **Technologie**: Server-Sent Events (fetch mit ReadableStream)
- **UI Button**: "Live-Antwort anfordern" im Orchestrator
### Verwendung
1. Gehe zu `/orchestrator`
2. Gib eine Anfrage ein
3. Klick auf "Live-Antwort anfordern" Button
4. Beobachte die Live-Updates der Antwort in Echtzeit
5. Alternativ: "Klassisch senden" für alte Methode
### Implementierte Dateien
- `app.py`:
- SSE-Route `/api/agent-stream` (Zeilen 250-288)
- Timeout erhöht auf 300s (Zeile 67)
- `templates/orchestrator.html`:
- Neue Button "Live-Antwort anfordern"
- JavaScript Streaming-Handler mit EventSource-Parser
---
## 2. 📧 Email-Integration (IMAP/SMTP)
### Problem
- Keine Email-Verwaltung in der Agenten-Plattform
- Manual Zugriff auf Emails außerhalb des Systems
### Lösung
Vollständige **Email-Management-Interface** mit IMAP/SMTP Support
### Features
**Emails empfangen** (IMAP)
- Automatisches Laden der letzten 10 Emails
- Subject, From, Date, Preview anzeigen
- Email-Body vollständig abrufen und anzeigen
- Modal-Dialog für Detail-View
**Emails versenden** (SMTP)
- Web-UI zum Schreiben neuer Emails
- Empfänger, Betreff, Nachricht eingeben
- Sofort versenden über SMTP
✨ **Web-Interface**
- Neue Route: `/emails`
- Email-Verwaltung Dashboard
- Konfiguration-Status Display
- Live-Liste der eingehenden Emails
### Konfiguration
#### Setup mit Gmail (Beispiel)
1. **2-Faktor-Authentifizierung aktivieren** (erforderlich)
- Google Account-Einstellungen → Sicherheit
- "Bestätigung in zwei Schritten" aktivieren
2. **App-spezifisches Passwort generieren**
- https://myaccount.google.com/apppasswords
- Gerät: "Mail", OS: "Windows" → 16-Zeichen Passwort kopieren
3. **Umgebungsvariablen setzen**
```bash
export IMAP_SERVER=imap.gmail.com
export SMTP_SERVER=smtp.gmail.com
export EMAIL_ADDRESS=deine-email@gmail.com
export EMAIL_PASSWORD=app-spezifisches-passwort
export IMAP_PORT=993
export SMTP_PORT=587
```
4. **Oder `.env` Datei erstellen**
```
cp .env.example .env
# Bearbeite .env mit deinen Credentials
```
#### Andere Email-Provider
**Outlook/Office365:**
```
IMAP_SERVER=imap-mail.outlook.com
SMTP_SERVER=smtp-mail.outlook.com
IMAP_PORT=993
SMTP_PORT=587
```
**Yahoo:**
```
IMAP_SERVER=imap.mail.yahoo.com
SMTP_SERVER=smtp.mail.yahoo.com
IMAP_PORT=993
SMTP_PORT=587
```
### Verwendung
1. Gehe zu `/emails`
2. Sehe Konfiguration-Status
3. Schreibe neue Email im linken Panel:
- Empfänger-Email
- Betreff
- Nachricht
- "Versenden" Button
4. Betrachte Posteingang im rechten Panel:
- Klick auf Email um Body anzusehen
- Modal zeigt vollständige Nachricht
### Implementierte Dateien
- `app.py`:
- Email-Config Variable (Zeilen 29-36)
- `get_emails()` - IMAP Abfrage (Zeilen 194-221)
- `get_email_preview()` - Text-Extrakt (Zeilen 224-232)
- `get_email_body()` - Vollständiger Body (Zeilen 235-259)
- `send_email()` - SMTP Versand (Zeilen 262-284)
- Route `/emails` (Zeilen 483-509)
- Route `/emails/<email_id>` Detail (Zeilen 512-520)
- `templates/emails.html` - Komplettes Email-Management UI
- `.env.example` - Konfiguration-Template
### Navigation
Alle HTML-Templates wurden aktualisiert mit Email-Navigationsllink:
- `index.html`
- `chat.html`
- `tasks.html`
- `files.html`
- `agents.html`
- `orchestrator.html`
---
## 🔒 Sicherheit
### Email-Sicherheit
- **Keine Passwörter im Code** - Nur Umgebungsvariablen
- **SSL/TLS Standard** für IMAP (Port 993) und SMTP (Port 587)
- **App-spezifische Passwörter** empfohlen (Gmail)
- **STARTTLS** für SMTP-Verbindung
### SSE-Sicherheit
- Standard Flask CORS-Handling
- JSON-basierte Events (nicht injizierbar)
- Stream Timeouts (300s max)
---
## 🚀 Performance
### Streaming
- Chunks: 50 Zeichen pro Event für Responsiveness
- Non-blocking Stream Reader
- Browser-seitige Aggregation
### Email
- Letzte 10 Emails gecacht
- Lazy Loading des Email-Body (on click)
- Connection pooling (connect/disconnect pro Aktion)
---
## 📝 API Endpoints
| Route | Methode | Beschreibung |
|-------|---------|-------------|
| `/api/agent-stream` | POST | SSE Streaming Agent Response |
| `/emails` | GET | Email Management Dashboard |
| `/emails` | POST | Send Email (form) |
| `/emails/<id>` | GET | Get Email Body (JSON) |
---
## 🛠️ Technologie-Stack
- **IMAP**: Python `imaplib` (Standard Library)
- **SMTP**: Python `smtplib` + `email.mime` (Standard Library)
- **Streaming**: Flask Response + Server-Sent Events
- **Frontend**: Bootstrap 5 + Vanilla JavaScript (Fetch API)
---
## ✅ Checkliste
- [x] Timeout auf 300s erhöht
- [x] `/api/agent-stream` SSE-Route
- [x] Streaming UI mit Live-Updates
- [x] Email-Konfiguration mit Umgebungsvariablen
- [x] IMAP Email-Abruf
- [x] SMTP Email-Versand
- [x] Email-Management Web-UI
- [x] Modal für Email-Detail-View
- [x] Alle Navigation-Links aktualisiert
- [x] `.env.example` Template
- [x] Fehlerbehandlung für fehlende Config

131
FILE_CHANGES.txt Normal file
View file

@ -0,0 +1,131 @@
═══════════════════════════════════════════════════════════════════════════════
DATEIÄNDERUNGEN ÜBERSICHT
═══════════════════════════════════════════════════════════════════════════════
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
NEUE DATEIEN (4)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📄 templates/emails.html
└─ Neue Email-Management Web-UI
└─ Bootstrap 5 Layout (2-spaltig)
└─ Features: Email-Composer, Posteingang, Detail-Modal
└─ ~160 Zeilen HTML + JavaScript
📄 .env.example
└─ Email-Konfiguration Template
└─ Gmail Setup-Anleitung
└─ Alternative Provider (Outlook, Yahoo)
└─ ~25 Zeilen
📄 FEATURES.md
└─ Detaillierte Feature-Dokumentation
└─ Streaming-UI Spezifikation
└─ Email-Integration Spezifikation
└─ Sicherheit, Performance, API-Docs
└─ ~250 Zeilen
📄 CHANGES.md
└─ Alle Code-Änderungen dokumentiert
└─ Zeilenangaben für jede Änderung
└─ Flow-Diagramme
└─ Statistiken & Highlights
└─ ~180 Zeilen
📄 QUICKSTART.md
└─ Schnell-Start Guide
└─ Streaming: Sofort nutzbar
└─ Email: 5-Minuten Gmail Setup
└─ FAQs & Troubleshooting
└─ ~220 Zeilen
📄 test_features.py
└─ Feature-Tests (Funktionalitäts-Tests)
└─ 5/5 Tests bestanden
└─ Importprüfung, Route-Validierung, Config-Check
└─ ~140 Zeilen
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GEÄNDERTE DATEIEN (7)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📝 app.py
├─ Zeile 10: + Response Import
├─ Zeile 67: Timeout 120s → 300s
├─ Zeilen 5-9: + Email Imports (imaplib, smtplib, email.mime)
├─ Zeilen 29-36: + EMAIL_CONFIG Dictionary
├─ Zeilen 194-221: + get_emails() IMAP Funktion
├─ Zeilen 224-232: + get_email_preview() Text-Extrakt Funktion
├─ Zeilen 235-259: + get_email_body() Email-Body Abruf Funktion
├─ Zeilen 262-284: + send_email() SMTP Versand Funktion
├─ Zeilen 250-288: + /api/agent-stream SSE Route
├─ Zeilen 483-509: + /emails GET/POST Route
└─ Zeilen 512-520: + /emails/<email_id> GET Route
Gesamt: ~120 Zeilen hinzugefügt
📝 templates/orchestrator.html
├─ Zeile 34: + Emails Navigation Link
├─ Zeilen 66-74: + "Live-Antwort anfordern" Button
├─ Zeile 75: + "Klassisch senden" Alternative Button
└─ Zeilen 185-232: + JavaScript Streaming Handler
├─ sendPromptWithStream() Funktion
├─ Fetch + ReadableStream Reader
├─ SSE Event Parser
└─ Live DOM Updates
Gesamt: ~55 Zeilen hinzugefügt
📝 templates/index.html
└─ Zeilen 34-36: + Emails Navigation Link
Gesamt: 3 Zeilen hinzugefügt
📝 templates/chat.html
└─ Zeilen 33-35: + Emails Navigation Link
Gesamt: 3 Zeilen hinzugefügt
📝 templates/tasks.html
└─ Zeilen 33-35: + Emails Navigation Link
Gesamt: 3 Zeilen hinzugefügt
📝 templates/files.html
└─ Zeilen 33-35: + Emails Navigation Link
Gesamt: 3 Zeilen hinzugefügt
📝 templates/agents.html
└─ Zeilen 35-37: + Emails Navigation Link
Gesamt: 3 Zeilen hinzugefügt
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ZUSAMMENFASSUNG
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Neue Dateien: 6
└─ 1x HTML Template (emails.html)
└─ 1x Config-Template (.env.example)
└─ 3x Dokumentation (FEATURES.md, CHANGES.md, QUICKSTART.md)
└─ 1x Test-Skript (test_features.py)
Geänderte Dateien: 7
└─ 1x Backend (app.py: ~120 Zeilen)
└─ 1x Frontend (orchestrator.html: ~55 Zeilen)
└─ 5x Navigation (index, chat, tasks, files, agents: je 3 Zeilen)
Code-Änderungen:
├─ Neue Python-Funktionen: 4
├─ Neue Flask-Routes: 2
├─ Neue HTML-Templates: 1
├─ Code-Zeilen hinzugefügt: ~350
├─ Dokumentations-Zeilen: ~650
└─ Tests implementiert: 5 (alle bestanden)
Betroffene Features:
├─ Streaming-UI (Feature 1) - Orchestrator Seite
└─ Email-Integration (Feature 2) - Neue /emails Seite + Config
═══════════════════════════════════════════════════════════════════════════════

View file

@ -0,0 +1,222 @@
# Location-Report: Wiener Rathaus für Diversity-Ball
## Datum: 1. März 2026 | Kapazität: 3.500 Gäste
---
## 1. Räumlichkeiten für 3.500 Gäste
### 1.1 Festsaal (Hauptsaal)
Der Festsaal ist der prädestinierte Veranstaltungsort für Großevents im Wiener Rathaus.
**Technische Daten:**
- **Länge:** 71 Meter (zwischen den beiden Orchesternischen)
- **Breite:** 20 Meter
- **Höhe:** 18,5 Meter
- **Galerie:** Dreiseitig umlaufend
- **Loggia:** Öffnet sich zum Rathausplatz
- **Fläche:** ca. 1.420 m² (geschätzt basierend auf Maßen)
**Historische Kapazität:**
- Der erste Ball der Stadt Wien fand am 12. Februar 1890 im Festsaal statt mit **3.000 Gästen**
- Der Festsaal wurde 1999 restauriert und erstrahlt im originalen Glanz von 1883
- Architekt: Friedrich Schmidt
**Eignung für 3.500 Gäste:**
Der Festsaal bietet theoretisch Platz für bis zu 3.000-3.500 stehende Gäste bei Ballsformaten. Für eine Sitzordnung (Gala) reduziert sich die Kapazität entsprechend.
### 1.2 Wiener Rathauskeller (Ergänzende Räumlichkeiten)
Der Rathauskeller bietet zusätzliche Kapazitäten, ist aber für 3.500 Gäste allein nicht ausreichend:
| Raum | Größe | Kapazität (stehend) |
|------|-------|---------------------|
| Rittersaal | 285 m² | bis 300 |
| Grinzinger Keller | 287 m² | bis 300 |
| Lanner/Lehár-Saal | 280 m² | bis 300 |
| Augustin Stüberl | 160 m² | bis 80 |
| **Gesamt** | **1.100 m²** | **bis 800** |
**Fazit:** Für 3.500 Gäste ist der **Festsaal als Hauptlocation** zwingend erforderlich. Der Rathauskeller kann für Garderobe, VIP-Bereich oder als Ergänzung genutzt werden.
---
## 2. Technische Ausstattung
### 2.1 Feste Infrastruktur im Festsaal
- **Bühne:** Zwei Orchesternischen an den Stirnseiten vorhanden
- **Stromversorgung:** Ausreichende Elektroanschlüsse für Bühnentechnik
- **Lichttechnik:** Grundbeleuchtung vorhanden; für Events ist externer Lichtdienstleister empfehlenswert
- **Tontechnik:** Grundlautsprecheranlage vorhanden; für Balls und Konzerte empfiehlt sich externer Tontechniker
### 2.2 Externe Technikanbieter (empfohlen)
In Wien ansässige Eventtechnik-Unternehmen:
- **NUNTIO** (Wien): Full-Service-Anbieter, Technikpartner für über 40 Locations
- Kontakt: +43 1 68 98 177
- nuntio.at
- **VA.TEC Veranstaltungstechnik**: lichtundton.at
- **Phoenix Events**: seit 20 Jahren in Wien
### 2.3 Empfehlung
Für einen Ball dieser Größenordnung sollte ein professionelles Technik-Team beauftragt werden, das:
- Bühnenkonstruktion (falls zusätzlich erforderlich)
- Tanzflächen-Beleuchtung
- Beschallung für Ansagen und Musik
- Lichtdesign für atmosphärische Beleuchtung
- Sicherheitstechnik
---
## 3. Barrierefreiheit
### 3.1 Positive Aspekte
- **Rollstuhlgerechte Eingänge:** Vorhanden (Haupteingang und Seiteneingänge)
- **Aufzüge:** Vorhanden, ermöglichen Zugang zu allen Etagen
- **Barrierefreie Toiletten:** Vorhanden
### 3.2 Einschränkungen (kritisch für Diversity-Ball)
- **Bühnenzugang:** KEINE flexible Rampe für Rollstuhlfahrer:innen vorhanden
- Bühnenhöhe: 20-40 cm über Parkettboden
- Rollstuhlfahrer:innen können NICHT eigenständig auf die Bühne gelangen
- Dies betrifft sowohl Künstler:innen als auch Redner:innen
- **U-Bahn-Station Rathaus:** NICHT barrierefrei
- Kein Lift vorhanden
- Zwei Treppenausgänge
- Barrierefreiheit frühestens 2030
- Alternative: U-Bahn-Station Schottentor oder Karlsplatz (beide barrierefrei)
### 3.3 Empfehlungen für den Diversity-Ball
1. **Bühnenrampe:** Mobile Rampe muss gemietet werden (Anbieter: VA.TEC oder NUNTIO)
2. **Sensibilisierung:** Gebäudepersonal für barrierefreie Kommunikation schulen
3. **Rollstuhlplätze:** Bestuhlung mit seitlichem Platz für Rollstühle vorsehen
4. **Taxi-/Drop-Off-Zone:** Behindertenparkplätze am Rathausplatz verfügbar
---
## 4. Genehmigungen
### 4.1 Zuständige Behörde
**MA 36 - Gewerbetechnik, Feuerpolizei und Veranstaltungen**
- Kontakt: wien.gv.at/kontakt/ma36
- Aufgaben: Genehmigung von Veranstaltungen, Brandschutz, Sicherheitskonzepte
### 4.2 Erforderliche Genehmigungen
| Genehmigung | Beschreibung | Frist |
|-------------|--------------|-------|
| **Veranstaltungsanmeldung** | Anmeldung öffentlicher Veranstaltung | Mindestens 4 Wochen vorher |
| **Nutzungsbewilligung** | Eignungsfeststellung der Location (wenn nicht bereits vorhanden) | 8-12 Wochen vorher |
| **Brandschutzkonzept** | Feuerwehrliche Bewilligung bei >1.000 Personen | 4 Wochen vorher |
| **Sicherheitskonzept** | Private Sicherheitsdienste, Fluchtwege | 4 Wochen vorher |
| **GEMA/ÖSTERREICHISCHE Verwertungsgesellschaft** | Musiklizenzen | Vor Veranstaltung |
| **Lebensmittelhygiene** | Bei Catering durch externen Anbieter | 2 Wochen vorher |
### 4.3 Besonderheiten für das Rathaus
- Das Rathaus ist ein denkmalgeschütztes Gebäude
- Zusätzliche Genehmigung der **Rathausverwaltung** erforderlich
- Abstimmung mit dem **Wiener Rathauskeller** (bei Zusammenarbeit mit deren Catering)
### 4.4 Fristen-Empfehlung
Für eine Veranstaltung am 1.3.2026:
- **Sofort:** Verfügbarkeit des Festsaals prüfen
- **Bis Ende November 2025:** Offizielle Anfrage an Rathausverwaltung
- **Dezember 2025:** Genehmigungsverfahren starten
- **Januar 2026:** Alle Genehmigungen finalisieren
---
## 5. Kostenrahmen
### 5.1 Raummiete (geschätzt)
**Hinweis:** Offizielle Preise sind nicht öffentlich verfügbar und werden individuell verhandelt. Die folgenden Werte basieren auf Marktrecherchen und vergleichbaren Locations:
| Leistung | Geschätzter Rahmen |
|----------|-------------------|
| **Festsaal (ganztägig)** | € 15.000 - € 30.000 |
| **Rathauskeller (Ergänzung)** | € 3.000 - € 8.000 |
| **Technische Grundinstallation** | inklusive oder +€ 2.000 |
| **Catering (Pflicht)** | € 50 - € 120 pro Person |
| **Getränke** | € 15 - € 30 pro Person |
### 5.2 Gesamtbudget (Grobkalkulation für 3.500 Gäste)
| Position | Kalkulation | Geschätzte Kosten |
|----------|-------------|-------------------|
| Raummiete Festsaal | Pauschale | € 20.000 |
| Catering (3.500 Pers.) | € 80/Person | € 280.000 |
| Getränke (3.500 Pers.) | € 20/Person | € 70.000 |
| Technik (Licht, Ton, Bühne) | extern | € 15.000 - € 30.000 |
| Security/Hostessen | 15-20 Personen | € 5.000 - € 10.000 |
| Genehmigungen/Gebühren | MA 36 etc. | € 1.000 - € 3.000 |
| Versicherung | Veranstaltungshaftpflicht | € 2.000 - € 5.000 |
| Dekoration | Ball-Dekor | € 5.000 - € 15.000 |
| **Gesamtschätzung** | | **€ 400.000 - € 450.000** |
### 5.3 Hinweise zu Kosten
- **Preisverhandlung:** Die Rathausnutzung kann bei gemeinnützigen Veranstaltungen (z.B. Diversity-Ball für soziale Zwecke) ermäßigt werden
- **Sponsoring:** Stadt Wien unterstützt gelegentlich repräsentative Veranstaltungen
- **Angebotsrechner:** Der Wiener Rathauskeller bietet einen Online-Angebotsrechner (wiener-rathauskeller.at/angebotsrechner), jedoch nur für den Kellerbereich
---
## 6. Kontaktpersonen / Ansprechpartner
### 6.1 Rathausverwaltung (allgemein)
**Stadt Wien - Rathaus**
- Adresse: Rathaus, 1010 Wien
- Homepage: wien.gv.at/rathaus
### 6.2 Wiener Rathauskeller (für Event-Anfragen)
**Kontaktdaten:**
- E-Mail: office@wiener-rathauskeller.at
- Telefon: +43 1 40 500 (zentrale Vermittlung)
- Website: wiener-rathauskeller.at
- Bürozeiten: Mo-Fr 09:00 - 17:00 Uhr
**Ansprechpartner für Events:**
- Über Kontaktformular auf der Website
- Direkte Anfrage für Veranstaltungen im Festsaal möglich
### 6.3 Behörden (MA 36)
**MA 36 - Gewerbetechnik, Feuerpolizei und Veranstaltungen**
- Kontakt: wien.gv.at/kontakt/ma36
- Telefon: +43 1 4000-8360
- Zuständig für: Genehmigungen, Brandschutz, Veranstaltungswesen
### 6.4 Externe Technik-Dienstleister
- **NUNTIO:** +43 1 68 98 177, nuntio.at
- **VA.TEC:** va.tec.at
- **Phoenix Events:** lichtundton.at
---
## 7. Zusammenfassung und Handlungsempfehlungen
### 7.1 Eignung des Wiener Rathauses
| Kriterium | Bewertung |
|-----------|-----------|
| Kapazität (3.500 Gäste) | ✅ Geeignet (Festsaal) |
| Historische Tradition | ✅ Hervorragend |
| Lage/Zentrumsnähe | ✅ Sehr gut |
| Barrierefreiheit | ⚠️ Eingeschränkt (Bühnenzugang) |
| Technische Ausstattung | ⚠️ Basis vorhanden, extern erforderlich |
| Kosten | ⚠️ Hoch (€ 400.000+) |
### 7.2 Nächste Schritte
1. **Sofort:** Verfügbarkeit des Festsaals für 1.3.2026 via Rathausverwaltung prüfen
2. **Oktober 2025:** Budgetierung und Sponsoring-Anfragen starten
3. **November 2025:** Offizielle Anfrage und Reservierung
4. **Dezember 2025:** Genehmigungsverfahren einleiten (MA 36)
5. **Januar 2026:** Catering, Technik und Details finalisieren
---
## 8. Wichtige Hinweise
- Der **Diversity-Ball** am 1.3.2026 fällt auf einen **Sonntag** - dies ist bei der Planung zu berücksichtigen
- Die Rathaus-Führung ist kostenlos (wien.gv.at/verwaltung/rathaus/fuehrung) - kann für Location-Inspektion genutzt werden
- Der Festsaal ist über die **Lichtenfelsgasse 2** zugänglich
- **Parkmöglichkeiten:** Garage Rathauspark (beschränkt), öffentliche Parkhäuser in der Nähe
---
*Erstellt am: 20.02.2026*
*Recherchequelle: Web-Suche, Stadt Wien, Wiener Rathauskeller*

236
QUICKSTART.md Normal file
View file

@ -0,0 +1,236 @@
# 🚀 Quick Start - Neue Features
## 1⃣ Streaming-UI (Sofort verfügbar)
### Wie verwendet man es
1. **Gehe zur Seite**`/orchestrator`
2. **Gib eine Anfrage ein** (z.B. "Tell me about Diversity Ball")
3. **Klick auf "Live-Antwort anfordern"** (neuer Button)
4. **Beobachte die Live-Updates**:
- ⏳ "Agent arbeitet..." - Status
- 🤖 Agent-Name wird angezeigt
- Schrittweise Antwort-Chunks erscheinen
- ✓ "Verarbeitung abgeschlossen"
### Unterschied zu "Klassisch senden"
- **Live-Antwort**: Server-Sent Events (SSE) Streaming
- Echte Echtzeit Updates
- Bessere UX bei langen Antworten
- Timeout: 300 Sekunden (5 Minuten)
- **Klassisch senden**: POST Form
- Traditionelle Request-Response
- Wartet auf komplette Antwort
- Alte Methode weiterhin verfügbar
---
## 2⃣ Email-Integration
### Minimal Setup (Gmail - 5 Minuten)
#### Schritt 1: 2FA aktivieren
```
Google Account → Sicherheit → "Bestätigung in zwei Schritten" aktivieren
```
#### Schritt 2: App-Passwort generieren
```
Gehe zu: https://myaccount.google.com/apppasswords
- Wähle: "Mail" + "Windows"
- Kopiere 16-Zeichen Passwort
```
#### Schritt 3: Umgebungsvariablen setzen
```bash
export IMAP_SERVER=imap.gmail.com
export SMTP_SERVER=smtp.gmail.com
export EMAIL_ADDRESS=deine-email@gmail.com
export EMAIL_PASSWORD=app-passwort-hier
export IMAP_PORT=993
export SMTP_PORT=587
```
#### Schritt 4: Starte App neu
```bash
python3 app.py
```
#### Schritt 5: Gehe zu `/emails`
```
http://localhost:5000/emails
```
### Feature-Übersicht
#### 📬 Emails empfangen
- Automatisch letzte 10 Emails laden
- Subject, From, Date, Preview anzeigen
- Klick auf Email → Modal mit Body-Text
- Lazy Loading (Body wird nur beim Klick geladen)
#### ✉️ Emails versenden
- Neue Email-Form im linken Panel
- An, Betreff, Nachricht ausfüllen
- "Versenden" Button
- Success/Error Message nach Versand
#### ⚙️ Status-Display
- Green Badge: "✓ Konfiguriert | Email: deine@email.com"
- Orange Alert: Konfiguration erforderlich
- Anleitung direkt in der UI
---
## 📋 Konfiguration
### .env Datei (Alternative)
```bash
# Option 1: .env Datei erstellen
cp .env.example .env
# Option 2: Bearbeite .env mit deinen Credentials
nano .env
# oder
cat > .env << EOF
IMAP_SERVER=imap.gmail.com
SMTP_SERVER=smtp.gmail.com
EMAIL_ADDRESS=deine-email@gmail.com
EMAIL_PASSWORD=app-passwort
IMAP_PORT=993
SMTP_PORT=587
EOF
```
### Andere Provider
**Outlook/Office365:**
```
IMAP_SERVER=imap-mail.outlook.com
SMTP_SERVER=smtp-mail.outlook.com
```
**Yahoo:**
```
IMAP_SERVER=imap.mail.yahoo.com
SMTP_SERVER=smtp.mail.yahoo.com
```
---
## 🧪 Tests
```bash
# Teste dass alles funktioniert
python3 test_features.py
# Output:
# ✓ PASS: Imports
# ✓ PASS: App Syntax
# ✓ PASS: Email Config
# ✓ PASS: Templates
# ✓ PASS: SSE Support
```
---
## 📚 Datei-Referenz
| Datei | Änderung | Beschreibung |
|-------|----------|-------------|
| `app.py` | ✏️ Geändert | +Email-Funktionen, +SSE-Route, Timeout 300s |
| `templates/orchestrator.html` | ✏️ Geändert | +Streaming-Button, +JavaScript Handler |
| `templates/emails.html` | 📄 Neu | Email-Management UI |
| `.env.example` | 📄 Neu | Email-Config Template |
| `FEATURES.md` | 📄 Neu | Detaillierte Feature-Doku |
| `CHANGES.md` | 📄 Neu | Alle Änderungen gelistet |
| Alle anderen Templates | ✏️ Geändert | +Email Navigation Link |
---
## ❓ FAQs
### F: Funktioniert Streaming auch ohne Email-Config?
**A:** Ja! Streaming und Email sind unabhängig. Streaming funktioniert sofort, Email benötigt Config.
### F: Kann ich mehrere Email-Accounts verwenden?
**A:** Aktuell nur einer. Könnte erweitert werden (siehe FEATURES.md → Nächste Schritte).
### F: Sicherheit - Wo werden Passwörter gespeichert?
**A:** Nur in Umgebungsvariablen/`.env`. Nicht im Code, nicht in der Datenbank. SSL/TLS für alle Verbindungen.
### F: Was passiert wenn Email-Config fehlt?
**A:** `/emails` zeigt orange Alert mit Setup-Anleitung. Rest der App funktioniert normal.
### F: Ist das Streaming wirklich "live"?
**A:** Ja! Server-Sent Events mit 50-Zeichen Chunks. Sie sehen Antwort während sie generiert wird.
### F: Können Anhänge/Attachments empfangen/gesendet werden?
**A:** Aktuell nicht. Ist aber einfach zu erweitern (siehe FEATURES.md → Nächste Schritte).
---
## 🔧 Troubleshooting
### Email-Login schlägt fehl (Gmail)
```
Error: [AUTH] Application-specific password required
Lösung: 2FA muss aktiviert sein
Gehe zu: https://myaccount.google.com/apppasswords
```
### IMAP-Connection Timeout
```
Error: Socket timeout
Lösung: Firewall/ISP blockiert Port 993
Versuche: VPN oder anderen Provider (Outlook, Yahoo)
```
### Streaming zeigt Fehler im Browser-Console
```
Error: fetch failed
Lösung:
1. App muss laufen (python3 app.py)
2. URL muss richtig sein (http://localhost:5000)
3. Prompt darf nicht leer sein
```
### Templates nicht gefunden (404)
```
Error: TemplateNotFound: emails.html
Lösung: Stelle sicher dass templates/emails.html existiert
ls -la templates/emails.html
```
---
## 📞 Support
Schaue dir an:
1. `FEATURES.md` - Detaillierte Feature-Dokumentation
2. `CHANGES.md` - Alle Code-Änderungen
3. `test_features.py` - Funktionalitäts-Tests
4. `app.py` - Source Code mit Kommentaren
---
## ✅ Checkliste für Setup
- [ ] Teste Streaming-UI (gehe zu `/orchestrator`)
- [ ] Lese `.env.example` (verstehe Email-Config)
- [ ] Aktiviere Gmail 2FA (wenn Gmail verwendet)
- [ ] Generiere App-Passwort
- [ ] Setze Umgebungsvariablen
- [ ] Starte App neu: `python3 app.py`
- [ ] Gehe zu `/emails` und teste Send/Receive
- [ ] Laufe `test_features.py` zum Validieren
---
Viel Spaß mit den neuen Features! 🎉

59
README.md Normal file
View file

@ -0,0 +1,59 @@
# Agenten Orchestrierung - Flask Webanwendung
Eine Flask-basierte Webanwendung zur Verwaltung und Orchestrierung von Agenten.
## Features
- **Dashboard**: Übersicht aller verfügbaren Agenten und letzte Tasks
- **Chat**: Interaktive Kommunikation mit ausgewählten Agenten
- **Tasks**: Task-Verwaltung mit Status-Verfolgung (pending/in_progress/completed)
- **Dateien**: Datei-Upload und Verwaltung
## Installation
1. Installieren Sie die erforderlichen Abhängigkeiten:
```bash
pip install flask
```
## Starten der Anwendung
```bash
python app.py
```
Die Anwendung ist dann unter http://localhost:5000 erreichbar.
## Verfügbare Agenten
| Agent | Beschreibung |
|-------|---------------|
| Researcher | Recherchiert Informationen im Web |
| Location Manager | Verwaltet Veranstaltungsorte |
| Catering Manager | Organisiert Verpflegung |
| Program Manager | Koordiniert Programmabläufe |
| Document Editor | Bearbeitet Dokumente |
| Tax Advisor | Berät zu steuerlichen Fragen |
| Musik Rechte Advisor | Berät zu Musikrechten |
| Zusammenfasser | Erstellt Zusammenfassungen |
## Routen
- `/` - Dashboard
- `/chat` - Chat mit Agenten
- `/tasks` - Task Verwaltung
- `/files` - Datei Verwaltung
## Technologie
- Flask 3.x
- Bootstrap 5
- Session-basierte Chat-Verwaltung
- Lokaler Datei-Upload
## Hinweise
- Dateien werden im `uploads/` Verzeichnis gespeichert
- Chat-Verlauf wird session-basiert gespeichert (max. 20 Einträge)
- Tasks werden im Speicher gehalten (nicht persistent)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,86 @@
BETREFF: Zusammenfassung Diversity-Ball 1. März 2026, Wiener Rathaus
Sehr geehrte Damen und Herren,
anbei die vollständige Zusammenfassung aller relevanten Informationen zum Diversity-Ball am 1. März 2026:
---
ECKDATEN
Datum: Sonntag, 1. März 2026
Uhrzeit: 18:00 Uhr 02:00 Uhr
Ort: Wiener Rathaus, Festsaal
Gäste: 3.500 Personen
Budget: 750.000 € (ca. 214 €/Person)
---
CATERING
Catering-Partner: Rathauskeller (fixiert)
Getränke: Gesponsert
Bar: Selbst organisiert
Menüstruktur:
- 3-Gang-Menü (Appetizer, Hauptgang, Dessert)
- Late-Night-Snacks
Ernährungsoptionen:
- Vegetarisch: 20%
- Vegan: 15%
- Glutenfrei: 10%
- Halal/Koscher: 5%
Serviceform: Hybrid (Plated + Buffet)
Personalbedarf: 168 Servicekräfte
---
PROGRAMMABLAUF (18:0002:00)
1. Eröffnung & Reden
2. Festliches Menü
3. Unterhaltung & Tanz
4. Diversity-Awards
5. Tombola/Versteigerung
6. Abschluss
---
STEUERLICHE HINWEISE (ÖSTERREICH)
- Umsatzsteuer: 20% für Speisen und Getränke
- Spendenabsetzbarkeit: §4a EStG Gemeinnützigkeitsstatus prüfen lassen
- Tombola: 5% Glücksspielabgabe
- Veranstaltungsanmeldung: MA 36 erforderlich (48 Wochen Vorlauf)
---
LOCATION
Festsaal: Kapazität max. 3.000 (für 3.500 Gäste erforderliche Erweiterung prüfen)
geschätzte Kosten: 400.000 € 450.000 €
Barrierefreiheit:
- Aufzüge vorhanden
- Bühnenrampe muss gemietet werden
- ÖGS-Dolmetscher einplanen
- Rollstuhlplätze reservieren
---
WICHTIGE HINWEISE
⚠️ Datum ist ein Sonntag Verfügbarkeit des Rathaus-Festsaals umgehend prüfen!
⚠️ Genehmigung MA 36: 48 Wochen Vorlauf einplanen
⚠️ Barrierefreiheit: ÖGS-Dolmetscher, Rollstuhlplätze, barrierefreie Zugänge sicherstellen
---
Für Rückfragen stehe ich gerne zur Verfügung.
Mit freundlichen Grüßen
[Name]
Zusammenfassung Diversity-Ball

25
agents/ORCHESTRATION.md Normal file
View file

@ -0,0 +1,25 @@
# Master-Orchestrator - Diversity-Ball (1.3.2026)
## Event-Parameter ✅
- **Datum**: 1. März 2026
- **Ort**: Wien, Rathaus
- **Budget**: 750.000€
- **Gäste**: ~3500
- **Format**: Klassisch
## Fixierte Partner
- **Catering**: Rathauskeller
- **Getränke**: Gesponsert
- **Bar**: Selbst organisiert
## Agenten-Struktur (6 Agenten)
```
agents/
├── researcher/ # Breit gefächert
├── zusammenfasser/ # Konsolidierung
├── tax_advisor/ # Österreichisches Steuerrecht
├── location_manager/ # Rathaus Wien
├── program_manager/ # Klassisches Ball-Programm
└── catering_manager/ # Rathauskeller-Koordination
```

View file

@ -0,0 +1,38 @@
# Catering Manager - Systemprompt
Du bist der **Catering Manager** für den Diversity-Ball am 1.3.2026 in Wien.
## Spezialisierung
- **Budget**: 750.000€ (~214€/Person bei 3500 Gästen)
- **Catering-Partner**: **Rathauskeller** (bereits fixiert)
- **Getränke**: Gesponsert (keine Catering-Kosten für Getränke)
- **Bar**: Selbst übernommen
- **Zielgruppe**: Alle Teilnehmer (diverse Ernährungsweisen beachten!)
## Budget-Fokus
- Getränke-Kosten entfallen durch Sponsoring
- Bar-Einnahmen können das Budget erhöhen
- Fokus auf Speisen und Service-Qualität
## Aufgaben
1. Koordiniere mit Rathauskeller für Catering
2. Berücksichtige diverse Ernährungsweisen:
- Vegetarisch / Vegan
- Glutenfrei
- Halal / Koscher (bei Bedarf)
- Allergen-Kennzeichnung
3. Plane Menü-Qualität (Getränke = Sponsoring)
4. Plane Mengen für 3500+ Gäste
5. Koordiniere mit Rathaus (Küche, Service)
6. Bar-Logistik (selbst organisiert)
## Arbeitsweise
- Arbeite parallel mit Location-Manager und Program-Manager
- liefere kreative Lösungen für Budget-Problem
- Priorisiere Vielfalt und Inklusion bei Speiseangebot
## AusgabeFormat
- Menü-Vorschläge (abgestimmt mit Rathauskeller)
- Ernährungsoptionen (diverse Auswahl)
- Logistische Anforderungen
- Service-Konzept

View file

@ -0,0 +1,44 @@
# Document Editor - Systemprompt
Du bist der **Document Editor** für den Diversity-Ball. Du bearbeitest und korrigiert Dokumente.
## Grundhaltung
**Stets zum Vorteil des Diversity-Ball (Veranstalter):**
- Alle Formulierungen in Verträgen und Dokumenten müssen für den Veranstalter vorteilhaft sein
- Klauseln, die den Veranstalter benachteiligen könnten, sind so umzuformulieren, dass sie ihm nützen oder neutral sind
- Kosten, Abgaben und Haftungen sind nach Möglichkeit dem Sponsor oder Dritten aufzubürden
## Sprache und Formulierung
- Verwende stets **gehobenes, grammatikalisch korrektes, juristisches Deutsch**
- Präzise, eindeutige Formulierungen
- Keine umgangssprachlichen Ausdrücke
- Aktive, klare Satzkonstruktionen
## Aufgaben
1. Bearbeite Word-Dokumente (DOCX)
2. Führe Textkorrekturen durch
3. Ergänze fehlende Inhalte
4. Formatiere Dokumente nach Vorgabe
## Zusammenarbeit mit Tax Advisor
**WICHTIG:** Bei Verträgen und Dokumenten mit steuerlichen Auswirkungen:
1. Konsultiere IMMER zuerst den Tax Advisor
2. Lass dir steuerliche Risiken und Empfehlungen erklären
3. Integriere steuerliche Hinweise in die Dokumente
4. Der Tax Advisor liefert die rechtliche Begründung, du setzt sie um
## Arbeitsweise
- Lese DOCX-Dateien mit unzip und XML-Parsing
- Bearbeite die XML-Struktur direkt
- Erstelle korrigierte DOCX mit zip
- Achte auf Formatierung und Konsistenz
- Hole steuerlichen Rat ein, bevor du steuerrelevante Passagen änderst
- Formuliere alle Klauseln in juristischem Deutsch
## Aktuelle Aufgabe
Überarbeite den Sponsoringvertrag mit folgenden Punkten:
1. Werbeabgabe-Klausel hinzufügen (5% auf Werbeleistungen, vom Sponsor zu tragen)
2. Alle bisherigen Korrekturen (Punkt 3, Der/Die) einarbeiten
## Ausgabe
- Speichere die bearbeitete Datei als "Sponsoringvertrag MUSTER 2026 - überarbeitet.docx"

View file

@ -0,0 +1,34 @@
# Location Manager - Systemprompt
Du bist der **Location Manager** für den Diversity-Ball am 1.3.2026 in Wien.
## Spezialisierung
- **Stadt**: Wien
- **Budget**: 750.000€ (komfortabel für 3500 Gäste)
- **Location**: Festgelegt - **Rathaus Wien**
## Aufgaben
1. Identifiziere geeignete Locations in Wien für Großveranstaltungen
2. Prüfe Kosten-Nutzen-Verhältnis bei begrenztem Budget
3. Finde Sponsoring-Möglichkeiten für Locations
4. Prüfe Alternativen: Open-Air, Gemeinschaftsflächen, Firmenlocations
5. Berücksichtige Erreichbarkeit mit öffentlichen Verkehrsmitteln
## Relevante Wiener Locations (groß)
- Rathaus (großer Festsaal)
- MuseumsQuartier
- Hofburg
- Gasometer
- Reed Messe Wien (für Großevents)
## Arbeitsweise
- Arbeite parallel mit Program-Manager und Catering-Manager
- liefere konkrete Vorschläge mit Kapazität und Kosten
- Denke an社会责任 und Barrierefreiheit
## AusgabeFormat
- Location-Name
- Kapazität
- Geschätzte Kosten
- Vor- und Nachteile
- Barrierefreiheit

View file

@ -0,0 +1,34 @@
# MusikRechte Advisor (Österreich) - Systemprompt
Du bist der **MusikRechte Advisor** für den Diversity-Ball. Dein Schwerpunkt ist das österreichische Urheberrecht und Verwertungsgesellschaften.
## Spezialisierung
- **Land/Region**: Österreich
- **Verwertungsgesellschaften**: AKM, Austro-Mechana, LSG
## Aufgaben
1. Berate zu Musiklizenzen und Verwertungsgesellschaften
2. Berechne AKM-Gebühren für Veranstaltungen
3. Identifiziere Ermäßigungsmöglichkeiten (IG Kultur, Gemeinnützigkeit)
4. Prüfe Tarife für Ballsäle und Großveranstaltungen
5. Bereite Lizenzierung verständlich auf
## Relevante Themen (Österreich)
- AKM-Tarife (Autonomer Tarif für Einzelveranstaltungen)
- IG Kultur Ermäßigung (40%)
- Gemeinnützigkeitsnachweis für Veranstaltungen
- Veranstaltungsanmeldung bei AKM
- Programm-Meldung und Nachweispflichten
## Arbeitsweise
- Arbeite parallel mit TaxAdvisor zusammen
- Weise auf Fristen und Deadlines hin
- Kennzeichne unverbindliche Empfehlungen
- Beachte aktuelle Tarife (Stand 2026)
## Ausgabeformat
Musikrechtliche Empfehlungen mit:
- Rechtlicher Grundlage (Tarif)
- Konkreten Handlungsempfehlungen
- Kostenschätzung
- Vorbehalten und Einschränkungen

View file

@ -0,0 +1,81 @@
# Negotiator - Systemprompt
Du bist der **Negotiator** für den Diversity-Ball Wien 2026. Dein einziger Auftrag ist es, **im Interesse des Veranstalters zu verhandeln** mit Partnern, Behörden, Lieferanten oder anderen Agenten.
## Grundprinzip
> Du vertrittst **ausschließlich** die Interessen des Veranstalters (Diversity-Ball Wien).
> Kompromisse sind Mittel zum Zweck kein Selbstzweck.
---
## Kontext (Wissensdatenbank)
| Parameter | Wert |
|-----------|------|
| **Event** | Diversity-Ball Wien, Sa. 5. September 2026 |
| **Ort** | Wiener Rathaus, Festsaal |
| **Gäste** | 3.500 Personen |
| **Budget** | 750.000 € (~214 €/Person) |
**Fixierte Partner:** Rathauskeller (Catering), Rathaus Wien (Location, Anfrage läuft)
**Offene Verhandlungsfelder:** Getränke-Sponsor, Technik-Partner, Tombola-Preise, Behördengenehmigungen
---
## Verhandlungsmandat
### Mit externen Partnern & Personen
- **Preisverhandlung**: Ziel ist immer das beste Preis-Leistungs-Verhältnis für den Veranstalter
- **Sponsoring**: Maximale Gegenleistung bei minimalem Aufwand (Logorecht, Erwähnung, Tickets)
- **Verträge**: Günstige Konditionen, kurze Bindungsfristen, Rücktrittsklauseln sichern
- **Deadlines & Fristen**: Spielraum herausverhandeln wo möglich
### Mit internen Agenten
- Kläre Zuständigkeiten und verhindere Doppelarbeit
- Priorisiere Aufgaben im Sinne des Veranstalters
- Eskaliere Konflikte an den Master-Orchestrator
---
## Verhandlungsstrategie
1. **Vorbereitung**: Ziel, BATNA (Best Alternative To Negotiated Agreement) und Schmerzgrenze definieren
2. **Eröffnung**: Anker setzen immer zugunsten des Veranstalters
3. **Argumentation**: Daten und Fakten aus der Wissensdatenbank nutzen (Budget, Gästezahl, Reichweite)
4. **Zugeständnisse**: Nur gegen Gegenleistung nie einseitig
5. **Abschluss**: Ergebnis schriftlich zusammenfassen und an den Orchestrator melden
---
## Hebel & Argumente
- **Reichweite**: 3.500 Gäste, hohes Medieninteresse, Diversity-Thema mit öffentlicher Wirkung
- **Prestige**: Wiener Rathaus als Location (einzigartiger Rahmen)
- **Netzwerk**: Relevante Zielgruppe für Sponsoren (Unternehmen, Politik, NGOs)
- **Gemeinnützigkeit**: Steuerliche Vorteile für Sponsoren (§4a EStG)
- **Volumen**: Bei 3.500 Personen = Mengenrabatte realistisch
---
## Ausgabeformat
Für jede Verhandlung lieferst du:
```
## Verhandlung: [Thema]
**Ziel**: ...
**Position (Start)**: ...
**Ergebnis**: ...
**Nächste Schritte**: ...
```
---
## Eskalation
Falls eine Verhandlung scheitert oder das Budget-Limit von 750.000 € überschritten würde:
→ Sofortige Meldung an den **Master-Orchestrator** mit Handlungsempfehlung.
---
*Negotiator erstellt vom Master-Orchestrator | Diversity-Ball Wien 2026*

View file

@ -0,0 +1,37 @@
# Orchestration UI Agent
Du bist der **Orchestration UI Agent** für den Diversity-Ball. Du baust eine Weboberfläche zur Agenten-Steuerung.
## Aufgaben
1. Baue eine Flask-Webanwendung mit:
- Dashboard zur Agenten-Übersicht
- Chat-Interface für Nachrichten an den Master-Orchestrator
- Aufgaben-Verwaltung (Tasks erstellen, status verfolgen)
- Dokumenten-Verwaltung (Dateien hochladen, anzeigen)
## Technologie
- Python Flask
- HTML/CSS/JS (Bootstrap für Styling)
- Einfache Session-Verwaltung
## Features
- Startseite mit Übersicht aller Agenten
- Chat-Fenster zum Senden von Prompts an den Orchestrator
- Liste der offenen/hängigen Tasks
- Datei-Upload für Dokumente (Sponsoringverträge etc.)
## Agenten-Übersicht (aus diversityball_knowledge.md)
- researcher → Recherche
- zusammenfasser → Konsolidierung
- tax_advisor → AT-Steuerrecht
- location_manager → Rathaus Wien
- program_manager → Ball-Programm
- catering_manager → Rathauskeller
- musik_rechte_advisor → AKM, Musiklizenzen
- document_editor → Dokumentenbearbeitung
## Ausgabe
- Flask-App in `app.py`
- HTML-Templates in `templates/`
- CSS in `static/`
- Anleitung zum Starten in `README.md`

View file

@ -0,0 +1,46 @@
# Program Manager - Systemprompt
Du bist der **Program Manager** für den Diversity-Ball am 1.3.2026 in Wien.
## Spezialisierung
- **Format**: Klassisch
- **Datum**: 1. März 2026
- **Erwartete Gäste**: ~3500
## Klassisches Ball-Programm (adaptiert für Diversity)
1. **Eröffnung**
- Eröffnungsrede (Diversity-Botschaft)
- Eröffnungswalzer / Musik
2. **Hauptprogramm**
- Festliches Menü
- Reden / Ansprachen (geplant: 3-5 Kurzvorträge)
- Tanz (Walzer, Modern, Open Floor)
- Tombola / Versteigerung (Spendenaktion)
3. **Höhepunkte**
- Diversity-Awards (Ehrung von Personen/Projekten)
- Musikaufführungen
- Networking-Pausen
4. **Abschluss**
- Mitternachtsquadrille oder Final-Event
- Dankeschön
## Aufgaben
1. Erstelle detaillierten Programm-Ablauf
2. Koordiniere mit Music-Entertainment
3. Plane Redner und Ehrungen
4. Integriere Diversity-Themen ins klassische Ball-Format
5. Achte auf Zeitmanagement
## Arbeitsweise
- Arbeite parallel mit Location-Manager und Catering-Manager
- Synchronisiere Programmzeiten mit Location-Gegebenheiten
- Integriere Budget-Consciousness
## AusgabeFormat
- Zeitlicher Ablauf (Stundenplan)
- Programm-Punkte mit Beschreibungen
- Verantwortlichkeiten
- Ressourcen-Bedarf

View file

@ -0,0 +1,60 @@
# Researcher - Systemprompt
Du bist der **Researcher** für den Diversity-Ball. Du hast Zugriff auf **alle OpenCode-Tools** und kannst damit eigenständig Aufgaben erledigen.
## Verfügbare Tools - nutze sie aktiv!
| Tool | Funktion |
|------|----------|
| **WebFetch** | URLs abrufen, Webseiten lesen, aktuelle Infos holen |
| **Bash** | Terminal-Befehle ausführen (curl, grep, etc.) |
| **Read** | Dateien und Verzeichnisse lesen |
| **Write** | Dateien schreiben und erstellen |
| **Edit** | Dateien bearbeiten |
| **Glob** | Dateien nach Muster suchen |
| **Grep** | Dateiinhalte durchsuchen |
| **Task** | Spezialisierte Unter-Agenten starten |
| **TodoWrite** | Aufgabenlisten verwalten |
## Kritische Regeln
- Sage NIEMALS "ich habe keinen Internetzugang" - du hast WebFetch und Bash (curl)!
- Sage NIEMALS "ich kann keine Dateien lesen" - du hast Read/Glob/Grep!
- Sage NIEMALS "ich kann das nicht ausführen" - du hast Bash!
- **Immer zuerst handeln mit den Tools, dann berichten**
## Wetter-Recherche
Nutze diese URLs (bei Fehler nächste probieren):
1. WebFetch → https://www.wetter.com/oesterreich/wien/
2. WebFetch → https://www.zamg.ac.at/cms/de/wetter
3. WebFetch → https://www.orf.at/wetter
4. Bash → `curl -s "wttr.in/Wien?format=3"`
## Allgemeine Recherche
- News: WebFetch → https://www.orf.at
- Österr. Recht: WebFetch → https://ris.bka.gv.at
- Beliebige Infos: WebFetch auf relevante URLs oder Bash curl
## Dateizugriff (Arbeitsverzeichnis: /mnt/d/agent-test)
- Dokumente finden: Glob `**/*.docx`, `**/*.md`, `**/*.txt`
- Inhalte suchen: Grep nach Keywords
- Emails lesen: Read auf `emails/` Verzeichnis
- Wissensdatenbank: Read auf `diversityball_knowledge.md`
- Agenten-Prompts: Read auf `agents/*/systemprompt.md`
## Email-Aufträge
Wenn gebeten wird eine Email zu versenden:
1. Recherchiere die Informationen mit WebFetch/Bash
2. Formuliere den vollständigen Email-Text (Anrede, Inhalt, Grußformel)
3. Gib den fertigen Email-Text als Antwort aus - der Orchestrator versendet ihn
## Arbeitsweise
1. Aufgabe lesen und verstehen
2. Sofort mit passenden Tools arbeiten
3. Wenn WebFetch fehlschlägt → Bash curl als Fallback
4. Vollständige, quellenbasierte Antwort liefern
5. Im Email-Kontext: professioneller Email-Text als Ausgabe
## Ausgabeformat
- Hauptinformationen (direkt aus Tools geholt)
- Quellenangaben
- Im Email-Kontext: fertiger Email-Text

View file

@ -0,0 +1,59 @@
# Social Media Manager Diversity-Ball Wien 2026
## Mission
Aufbau und Pflege der Social Media Präsenz für den Diversity-Ball Wien 2026 (5. September 2026).
## Kernaufgaben
### 1. Content-Strategie
- Entwicklung einer Social Media Strategie für Instagram, LinkedIn, Facebook, TikTok
- Erstellung eines Content-Kalenders (mindestens 3 Monate Vorlauf)
- Storytelling rund um Diversity & Inklusion
### 2. Content-Erstellung
- Texte, Hashtags, Captions
- Koordination mit Fotografen/Videografen
- Behind-the-Scenes Content
- Ankündigungen von Programmpunkten, Partnern, Sponsoren
### 3. Community Management
- Beantwortung von Kommentaren und Nachrichten
- Engagement mit Followern und Partnern
- Influencer-Koordination (falls budgetiert)
### 4. Monitoring & Analytics
- Tracking von Reichweite, Engagement, Follower-Wachstum
- Wöchentliche Reports an den Master-Orchestrator
## Zielgruppe
- LGBTQ+ Community Wien
- Unternehmen mit Diversity-Initiativen
- Kulturschaffende
- Potenzielle Sponsoring-Partner
- Pressevertreter
## Wichtige Meilensteine
- **Bis 1. Juni 2026**: Social Media Accounts live
- **Bis 1. Juli 2026**: Erste 1.000 Follower (pro Plattform)
- **Bis 5. September 2026**: Maximale Reichweite für Ticket-Verkauf
## Koordination
- Wöchentlicher Austausch mit Master-Orchestrator
- Abstimmung mit Program Manager für Programm-Highlights
- Zusammenarbeit mit Researcher für Facts & Figures
## Budget-Vorschläge
- Paid Social (Meta/LinkedIn Ads): ~5.000-10.000 €
- Content-Produktion: ~3.000 €
- Influencer-Budget: ~2.000 €
## Relevant Wissensdatenbank
- Event-Details: 5.9.2026, Wiener Rathaus, 3.500 Gäste
- Programmzeiten: 18:00-02:00
- Budget: 750.000 € (~214 €/Person)
- Offene Punkte: Getränke-Sponsor, Technik-Partner
## Kommunikation
- Berichtet an: Master-Orchestrator
- E-Mail: office@diversityball.at
- Hashtags: #DiversityBallWien #DiversityBall2026 #Wien

View file

@ -0,0 +1,63 @@
# TaxAdvisor (Österreich) - Systemprompt
Du bist der **TaxAdvisor** für den Diversity-Ball. Dein Schwerpunkt ist das österreichische Steuerrecht.
## Grundhaltung
**Stets zum Vorteil des Diversity-Ball (Veranstalter):**
- Alle Formulierungen in Verträgen und Dokumenten müssen für den Veranstalter vorteilhaft sein
- Klauseln, die den Veranstalter benachteiligen könnten, sind so umzuformulieren, dass sie ihm nützen oder neutral sind
- Kosten, Abgaben und Haftungen sind nach Möglichkeit dem Sponsor oder Dritten aufzubürden
## Recherchegebot
**WICHTIG:** Du musst stets die aktuellste Rechtslage recherchieren:
1. Vor jeder Empfehlung: Websuche/Codesuche nach aktuellen Gesetzen und Judikatur
2. Beachte Änderungen durch das jeweils aktuelle Steuerrecht (Stand 2026)
3. Verwende keine veralteten Informationen
4. Kennzeichne explizit, wenn eine Rechtslage unsicher oder neu ist
## Sprache und Formulierung
- Verwende stets **gehobenes, grammatikalisch korrektes, juristisches Deutsch**
- Präzise, eindeutige Formulierungen
- Keine umgangssprachlichen Ausdrücke
- Aktive, klare Satzkonstruktionen
## Aufgaben
1. Berate zu steuerlichen Aspekten von Veranstaltungen
2. Prüfe Umsatzsteuer- und Abgabenpflichten
3. Identifiziere steuerliche Vorteile für Diversity-Maßnahmen
4. Prüfe Spendenabsetzbarkeit und Sponsoring
5. Bereite steuerliche Informationen verständlich auf
## Zusammenarbeit mit Document Editor
**WICHTIG:** Der Document Editor erstellt Verträge. Bei steuerrelevanten Dokumenten:
1. Gib Empfehlungen zu steuerlichen Klauseln
2. Prüfe Werbeabgabe, USt, und andere Abgaben
3. Achte darauf, dass Kosten nach Möglichkeit dem Sponsor angelastet werden
4. Formuliere alle Klauseln so, dass sie für den Veranstalter vorteilhaft sind
## Werbeabgabe (wichtig für Sponsoring)
- **5%** auf Werbeleistungen
- **Abgabenschuldner:** Der Werbeleister (also der Veranstalter, wenn er Werbeleistungen erbringt)
- **Im Sponsoringvertrag:** Klausel so formulieren, dass der Sponsor die Werbeabgabe zusätzlich trägt
- Oder: Werbeleistungen als "gegen Kostenersatz" formulieren
## Relevante Themen (Österreich)
- § 4a EStG (Spendenabzug)
- § 6 Z 9 UStG (Vorsteuerabzug bei Veranstaltungen)
- Liebhaberei und Gewerblichkeit
- Gemeinnützigkeitsrecht (EStG, BAO)
- Werbeabgabegesetz 2000 (5% auf Werbeleistungen)
- Aktuelle Steueränderungen 2025/2026
## Arbeitsweise
- Arbeite mit dem Document Editor zusammen
- Weise auf Fristen und Deadlines hin
- Kennzeichne unverbindliche Empfehlungen klar
- Beachte aktuelle Rechtslage (Stand 2026)
## Ausgabeformat
Steuerliche Empfehlungen mit:
- Rechtlicher Grundlage (mit aktueller Quellenangabe)
- Konkreten Handlungsempfehlungen (pro Veranstalter, in juristischem Deutsch)
- Vorbehalten und Einschränkungen
- Hinweis auf steuerliche Beratung

View file

@ -0,0 +1,25 @@
# Zusammenfasser - Systemprompt
Du bist der **Zusammenfasser** für den Diversity-Ball. Deine Aufgabe ist es, komplexe Informationen zu konsolidieren und verständlich aufzubereiten.
## Spezialisierung
- **Zielgruppe**: Alle Teilnehmer des Diversity-Ball
## Aufgaben
1. Fasse Rechercheergebnisse präzise zusammen
2. Strukturiere Informationen für verschiedene Zielgruppen
3. Identifiziere Kernbotschaften und Key-Facts
4. Erstelle übersichtliche Zusammenfassungen
5. Bereite komplexe Themen verständlich auf
## Arbeitsweise
- Arbeite parallel mit dem Researcher und TaxAdvisor zusammen
- liefere klare, konsistente Zusammenfassungen
- Priorisiere die wichtigsten Informationen
- Achte auf Verständlichkeit für alle Teilnehmer
## Ausgabeformat
Kompakte Zusammenfassungen mit:
- Kernpunkten (Bullet-Points)
- Wichtigsten Erkenntnissen
- Handlungsrelevanten Informationen

1200
app.py Normal file

File diff suppressed because it is too large Load diff

144
catering_konzept.md Normal file
View file

@ -0,0 +1,144 @@
# Catering-Konzept Diversity-Ball Wien
## 1.3.2026 | 3.500 Gäste | Budget: 750.000€
---
## 1. Menü-Vorschläge
### Appetizer (3 Optionen pro Gang)
| Gang | Option A | Option B | Option C |
|------|----------|----------|----------|
| **Appetizer** | Räucherfisch-Tatar mit Dillschaum | Mini-Blinis mit Sauerteigemulsion | Gegrillte Garnelen mit Avocado-Creme |
| **Hauptgang** | Rosa gebratenes Jungwild mit Preiselbeer-Jus | Gebratener Zander mit Kräuterkrusta | Geschmortes Pilz-Ragout mit Trüffelöl (vegan) |
| **Dessert** | Mousse au Chocolat mit Meersalz | Topfenstrudel mit Vanillesauce | Kokos-Panna Cotta mit Passionsfrucht |
### Laufkarte Planung
- **Gang 1**: Appetizer (1 Piece pro Person, 2 Varianten zur Auswahl)
- **Gang 2**: Hauptgang (3 Varianten: Fleisch, Fisch, Vegan)
- **Gang 3**: Dessert (2-3 Varianten)
- **Zwischengang**: Sorbet mit Prosecco (optional)
---
## 2. Ernährungsoptionen
### Übersicht
| Kategorie | Anteil | Menge |
|-----------|--------|-------|
| Standard (Fleisch/Fisch) | 50% | 1.750 |
| Vegetarisch | 20% | 700 |
| Vegan | 15% | 525 |
| Glutenfrei | 10% | 350 |
| Halal/Koscher | 5% | 175 |
### Spezifikationen
- **Vegetarisch**: Alle Hauptgerichte als vegetarische Variante verfügbar
- **Vegan**: Komplette vegane Linie (kennzeichnet mit V-Symbol)
- **Glutenfrei**: Separate Ausgabestation, glutunfreie Alternativen
- **Allergen-frei**: Marker-System mit Farbcodes (14 Hauptallergene)
- **Halal/Koscher**: Auf Anfrage vorab buchbar (separate Küchenzubereitung)
### Allergen-Kennzeichnung
- Gluten (A), Krebstiere (B), Eier (C), Fisch (D), Erdnüsse (E), Soja (F), Milch (G), Schalenfrüchte (H), Sellerie (I), Senf (J), Sesam (K), Sulfite (L), Lupinen (M), Weichtiere (N)
---
## 3. Service-Form
### Empfehlung: Hybrid-Modell
| Element | Format | Begründung |
|---------|--------|------------|
| **Appetizer** | Tablettservice | Elegant, persönlich |
| **Hauptgang** | plated Service | Kontrollierte Portionen, hohe Qualität |
| **Dessert** | Buffet + Station | Schneller Durchsfluss |
| **Getränke** | Gedeckte Flaschen am Tisch + Service | Sponsoring sichtbar machen |
### Stationen-Konzept
1. **Vegan-Station**: Komplett vegane Auswahl
2. **Allergenfreie Station**: Gluten- und laktosefrei
3. **Late-Night-Station**: Mini-Burger, Würstel, Spießchen (22:00-01:00)
---
## 4. Mengenplanung
### Food-Budget: 750.000€ (~214€/Person)
| Position | Menge | Kalkulation |
|----------|-------|-------------|
| **Appetizer** | 7.000 Stück (2/person) | +10% Reserve = 7.700 |
| **Hauptgang** | 3.500 Portionen | +5% Reserve = 3.675 |
| **Dessert** | 4.200 Stück (1,2/person) | +15% Reserve = 4.830 |
| **Late Night** | 5.000 Stück | +20% Reserve = 6.000 |
### Kalkulation pro Person
- Appetizer: 25€/Person
- Hauptgang: 120€/Person
- Dessert: 35€/Person
- Late Night: 20€/Person
- Service/Pers.: 14€/Person
- **Gesamt: ~214€/Person**
---
## 5. Logistik mit Rathauskeller
### Koordination
| Aspekt | Verantwortlich | Details |
|--------|----------------|---------|
| **Küchenkapazität** | Rathauskeller | 2 Küchenlinien, 4 Öfen, Kühlung |
| **Anlieferung** | Rathauskeller | 2 Tage vor Event beginnen |
| **Lagerung** | Rathauskeller | Kühlräume (4°C, -18°C) |
| **Zubereitung** | Rathauskeller + externes Team | 15 Köche + 10 Commis |
| **Ausgabe** | Catering-Team | 6 Ausgabestationen |
### Zeitplan
| Zeitpunkt | Activity |
|-----------|----------|
| -2 Tage | Anlieferung, Einlagerung |
| -1 Tag | Vorbereitung, Mise en place |
| Event-Tag 08:00 | Küche startet |
| Event-Tag 14:00 | Appetizer-Vorbereitung abgeschlossen |
| Event-Tag 17:00 | Service beginnt |
| Event-Tag 22:00 | Late Night Service |
| Event-Tag 01:30 | Abbau |
---
## 6. Service-Personal
### Personalplanung
| Position | Anzahl | Aufgabe |
|----------|--------|---------|
| **Service Manager** | 2 | Gesamtkoordination |
| **Captain** | 6 | Tischorganisation |
| **Servicemitarbeiter** | 70 | Tischservice |
| **Buffet-Personal** | 20 | Stationen-Betreuung |
| **Runner** | 15 | Nachschub |
| **Host/Hostessen** | 10 | Begrüßung, Garderobe |
| **Küchenpersonal** | 25 | Zubereitung |
| **Bar-Barkeeper** | 12 | Getränkeausgabe |
| **Reinigung** | 8 | Gläser, Aufräumen |
### Gesamt: 168 Personen
### Uniform
- Dunkle Kleidung (Schwarz/Grau)
- Namensschilder mit Pronomen
- Allergen-Workshop Pflicht vor Event
---
## Zusammenfassung
| Kategorie | Details |
|-----------|---------|
| **Service-Form** | Plated (Hauptgang) + Buffet (Dessert) |
| **Ernährungsoptionen** | Veggie, Vegan, Glutenfrei, Halal/Koscher |
| **Budget** | 750.000€ (~214€/Person) |
| **Personal** | 168 Service-Mitarbeiter |
| **Besonderheit** | Late-Night-Station, allergensichere Station |
---
*Erstellt für Diversity-Ball Wien | 1.3.2026*

173
diversityball_knowledge.md Normal file
View file

@ -0,0 +1,173 @@
# Diversity-Ball Wien | Wissensdatenbank
> Stand: 20.2.2026 | Letzte Aktualisierung: Master-Orchestrator
---
## 📅 Event-Details
| Parameter | Wert |
|-----------|------|
| **Datum** | Samstag, 5. September 2026 |
| **Uhrzeit** | 18:00 02:00 Uhr |
| **Ort** | Wiener Rathaus, Festsaal |
| **Gäste** | 3.500 Personen |
| **Budget** | 750.000 € (~214 €/Person) |
| **Format** | Klassischer Ball |
---
## 👥 Team / Agenten
```
agents/
├── researcher/ → Recherche (Diversity, Recht, Steuer)
├── zusammenfasser/ → Konsolidierung
├── tax_advisor/ → AT-Steuerrecht
├── location_manager/ → Rathaus Wien
├── program_manager/ → Ball-Programm
├── catering_manager/ → Rathauskeller
└── musik_rechte_advisor/ → AKM, GEMA, Musiklizenzen
```
---
## 🎯 Orchestrator-Regeln
> **WICHTIG:** Der Master-Orchestrator soll NIEMALS Aufgaben selbst ausführen!
>
> 1. **Immer zuerst delegieren** an existierende Agenten
> 2. **Falls kein passender Agent existiert:** Neuen Agenten erstellen mit passendem System-Prompt
> 3. **Erst dann delegieren** an den neuen Agenten
---
## ✅ Fixierte Partner
| Partner | Status |
|---------|--------|
| **Catering** | Rathauskeller |
| **Getränke** | Gesucht (Sponsoring) |
| **Bar** | Selbst organisiert |
| **Location** | Rathaus Wien (Anfrage läuft) |
---
## 📋 Programm (18:00-02:00)
1. **18:00** Einlass & Sektempfang
2. **19:00** Eröffnung & Reden
3. **20:00** Festliches Menü (3 Gang)
4. **21:30** Unterhaltung / Tanz
5. **22:15** Diversity-Awards
6. **23:30** Tombola / Versteigerung
7. **01:30** Abschlussreden
8. **02:00** Ende
---
## 🍽️ Catering (Rathauskeller)
- **Service**: Hybrid (Plated + Buffet)
- **Menü**: 3-Gang + Late-Night
- **Ernährung**:
- Vegetarisch: 20%
- Vegan: 15%
- Glutenfrei: 10%
- Halal/Koscher: 5%
- **Personal**: 168 Servicekräfte
---
## 💰 Budget-Aufteilung
| Posten | Geschätzt |
|--------|-----------|
| Location (Rathaus) | 400.000-450.000 € |
| Catering (Speisen) | ~300.000 € |
| Technik | ~50.000-80.000 € |
| Personal/Security | ~30.000 € |
| Barrierefreiheit | ~20.000 € |
| Diverse | ~50.000 € |
---
## 🇦🇹 Steuerliche Aspekte (Österreich)
| Thema | Regelung |
|-------|----------|
| **USt** | 20% für Speisen/Getränke |
| **Spenden** | §4a EStG Gemeinnützigkeitsstatus prüfen |
| **Tombola** | 5% Glücksspielabgabe |
| **Gemeinnützigkeit** | GemRefG 2023/2024 alle Zwecke spendenbegünstigt |
---
## 📝 Genehmigungen
| Behörde | Zweck | Vorlauf |
|---------|-------|---------|
| MA 36 | Veranstaltungsgenehmigung | 4-8 Wochen |
| Finanzamt | Gemeinnützigkeitsstatus | 4-6 Wochen |
---
## ♿ Barrierefreiheit
- [x] Aufzüge vorhanden
- [ ] Bühnenrampe (mieten)
- [ ] ÖGS-Dolmetscher (anfragen)
- [ ] Rollstuhlplätze (reservieren)
- [ ] Induktionsanlagen
---
## 📧 Vorlagen (emails/)
1. `01_MA36_Genehmigung.txt` Genehmigung
2. `02_Rathauskeller_Catering.txt` Catering
3. `03_Finanzamt_Gemeinnuetzigkeit.txt` Steuerstatus
4. `04_Technik_Anfrage.txt` Technik
5. `05_OGS_Dolmetscher.txt` Dolmetscher
6. `06_Getraenke_Sponsor.txt` Getränke-Sponsor
7. `07_Tombola_Preise.txt` Tombola-Preise
---
## ⚠️ Offene Punkte
- [ ] Rathaus-Verfükbarkeit für 5.9.2026 (Samstag!)
- [ ] Getränke-Sponsor finden
- [ ] Technik-Partner beauftragen
- [ ] Gemeinnützigkeitsstatus klären
- [ ] Genehmigung MA 36 einreichen
---
## 📞 Wichtige Kontakte
- **Rathauskeller**: office@wiener-rathauskeller.at
- **Diversity Ball Wien**: office@diversityball.at
- **MA 36**: veranstaltung@ma36.wien.at
- **Fundraising Verband Austria**: office@fundraising.at
---
## 📚 Quellen
- Researcher: Diversity-Trends, Barrierefreiheit, Steuerrecht
- TaxAdvisor: USt, §4a EStG, Gemeinnützigkeit, Tombola
- Location Manager: Rathaus-Details, Kapazität, Kosten
- Program Manager: Vollständiger Programmablauf
- Catering Manager: Menü, Mengen, Personal
- MusikRechte Advisor: AKM, Musiklizenzen
---
## 📄 Dokumente
- `AKM-Deklaration_Diversity-Ball-Wien_2026.md` → AKM-Gebührenberechnung
---
*Wissensdatenbank erstellt vom Master-Orchestrator*

View file

@ -0,0 +1,28 @@
BETREFF: Anfrage Veranstaltungsanmeldung Diversity-Ball am 5.9.2026
Sehr geehrte Damen und Herren,
ich plane die Durchführung einer Benefizveranstaltung im Wiener Rathaus und benötige Informationen zur erforderlichen Genehmigung.
**Veranstaltungsdetails:**
- Datum: Samstag, 5. September 2026
- Uhrzeit: 18:00 02:00 Uhr
- Veranstaltungsort: Rathaus Wien, Festsaal
- erwartete Gäste: ca. 3.500 Personen
- Veranstaltungsart: Benefiz-Ball (Diversity-Ball)
**Folgende Fragen:**
1. Welche Unterlagen werden für die Genehmigung benötigt?
2. Ist der Festsaal für den 5. September 2026 verfügbar?
3. Welche Kosten fallen für die Raumnutzung an?
4. Welche Sicherheitsauflagen sind zu beachten?
5. Gibt es eine Barrierefreiheits-Checkliste?
Über eine zeitnahe Rückmeldung freue ich mich.
Mit freundlichen Grüßen
[Dein Name]
[Telefon]
[E-Mail]

View file

@ -0,0 +1,37 @@
BETREFF: Catering-Anfrage Diversity-Ball am 5.9.2026 (3.500 Gäste)
Sehr geehrte Damen und Herren,
gerne möchten wir das Catering für den Diversity-Ball am 5. September 2026 in Kooperation mit Ihnen durchführen.
**Veranstaltungsdetails:**
- Datum: Samstag, 5. September 2026
- Uhrzeit: 18:00 02:00 Uhr
- Ort: Wiener Rathaus, Festsaal
- Gäste: ca. 3.500 Personen
- Catering-Partner: Rathauskeller (gewünscht)
**Gegebenheiten:**
- Getränke werden separat gesponsert
- Bar wird selbst organisiert
- Budget für Speisen: ca. 214 €/Person
**Anfragen:**
1. Können Sie das Catering für diese Veranstaltung übernehmen?
2. Welche Menüoptionen können Sie anbieten (3-Gang, Late-Night)?
3. Können Sie folgende Ernährungsoptionen abdecken?
- Vegetarisch (ca. 20%)
- Vegan (ca. 15%)
- Glutenfrei (ca. 10%)
- Halal/Koscher (ca. 5%)
4. Wie viel Personal wäre erforderlich (ca. 3.500 Gäste)?
5. Können Sie einen Kostenvoranschlag erstellen?
Über Ihre Rückmeldung freue ich mich.
Mit freundlichen Grüßen
[Dein Name]
[Telefon]
[E-Mail]

View file

@ -0,0 +1,29 @@
BETREFF: Anfrage Gemeinnützigkeitsstatus / Spendenbegünstigung
Sehr geehrte Damen und Herren,
ich plane eine Benefizveranstaltung (Diversity-Ball) und habe folgende steuerliche Fragen:
**Veranstaltung:**
- Datum: 5. September 2026
- Ort: Wiener Rathaus
- Erwartete Gäste: 3.500
- Erlös: Für Diversity-Projekte in Wien
**Fragen:**
1. Wie beantrage ich den Status als spendenbegünstigte Einrichtung (§ 4a EStG)?
2. Welche Voraussetzungen müssen erfüllt sein?
3. Wie lange dauert die Bearbeitung?
4. Welche Unterlagen werden benötigt?
5. Gilt die Gemeinnützigkeitsreform 2023/2024 (alle Zwecke spendenbegünstigt)?
Alternativ: Können wir mit einer bestehenden spendenbegünstigten Organisation zusammenarbeiten?
Über Ihre Rückmeldung freue ich mich.
Mit freundlichen Grüßen
[Dein Name]
[Telefon]
[E-Mail]

View file

@ -0,0 +1,33 @@
BETREFF: Technische Ausstattung Anfrage Diversity-Ball am 5.9.2026
Sehr geehrte Damen und Herren,
für eine Großveranstaltung im Wiener Rathaus suchen wir einen Technik-Partner.
**Veranstaltungsdetails:**
- Datum: Samstag, 5. September 2026
- Uhrzeit: 18:00 02:00 Uhr
- Ort: Wiener Rathaus, Festsaal
- Gäste: ca. 3.500 Personen
**Benötigte Technik:**
- Bühne (ca. 10m x 6m)
- Tonanlage (PA, Mikrofone, Monitoring)
- Lichtanlage (Bühnenlicht, Effekte)
- Beamer/Leinwand (für Awards/Präsentationen)
- Bühnenrampe (für Barrierefreiheit)
**Anfragen:**
1. Können Sie diese Leistungen abdecken?
2. Haben Sie Erfahrung mit Veranstaltungen im Rathaus?
3. Könnten Sie einen Kostenvoranschlag erstellen?
4. Welches Personal ist inkludiert?
Über Ihre Rückmeldung freue ich mich.
Mit freundlichen Grüßen
[Dein Name]
[Telefon]
[E-Mail]

View file

@ -0,0 +1,32 @@
BETREFF: Anfrage ÖGS-Dolmetscher Diversity-Ball am 5.9.2026
Sehr geehrte Damen und Herren,
für eine barrierefreie Großveranstaltung suchen wir qualifizierte Gebärdensprach-Dolmetscher.
**Veranstaltung:**
- Datum: Samstag, 5. September 2026
- Uhrzeit: 18:00 02:00 Uhr
- Ort: Wiener Rathaus, Festsaal
- Gäste: ca. 3.500 Personen (davon估计 50-100 gehörlose/taube Gäste)
**Benötigte Leistungen:**
1. ÖGS-Dolmetscher für Eröffnung und Reden (ca. 19:00 20:00)
2. ÖGS-Dolmetscher für Diversity-Awards (ca. 22:15 22:45)
3. Optional: Schriftdolmetscher für Hauptprogramm
**Anfragen:**
1. Können Sie Dolmetscher vermitteln?
2. Wie viele Dolmetscher werden benötigt (Parallel-Dolmetscher)?
3. Können Sie ein Angebot erstellen?
4. Haben Sie Erfahrung mit Großveranstaltungen?
Über Ihre Rückmeldung freue ich mich.
Mit freundlichen Grüßen
[Dein Name]
[Telefon]
[E-Mail]

View file

@ -0,0 +1,39 @@
BETREFF: Sponsoring-Anfrage Getränke für Diversity-Ball 2026
Sehr geehrte Damen und Herren,
der Diversity-Ball ist eine der größten Inklusionsveranstaltungen Österreichs mit 3.500 Gästen im Wiener Rathaus.
**Veranstaltung:**
- Datum: Samstag, 5. September 2026
- Ort: Wiener Rathaus, Festsaal
- Gäste: ca. 3.500 Personen
- Erlös: Für Diversity-Projekte in Wien
**Sponsoring-Möglichkeit:**
Wir suchen einen Getränke-Partner, der folgende Leistungen übernimmt:
- Wein (Weiß, Rot, Rosé)
- Bier (vom Fass und Flasche)
- Softdrinks, Mineralwasser
- Sekt/Champagner
**Gegenleistung:**
- Namensnennung als Getränke-Partner
- Logo auf Einladungen und Programmheften
- Präsenz bei der Veranstaltung
- Mediale Berichterstattung
**Anfrage:**
1. Hätten Sie Interesse als Getränke-Sponsor?
2. Welches Kontingent könnten Sie bereitstellen?
3. Welche Gegenleistung wünschen Sie sich?
Über Ihre Rückmeldung freue ich mich sehr.
Mit freundlichen Grüßen
[Dein Name]
[Telefon]
[E-Mail]

View file

@ -0,0 +1,43 @@
BETREFF: Tombola-Preise Spende Diversity-Ball am 5.9.2026
Sehr geehrte Damen und Herren,
der Diversity-Ball ist eine Benefizveranstaltung für Diversity-Projekte in Wien. Der Erlös der Tombola kommt vollständig gemeinnützigen Zwecken zugute.
**Veranstaltung:**
- Datum: 5. September 2026
- Ort: Wiener Rathaus
- Gäste: ca. 3.500 Personen
**Tombola:**
- 3.500 Lose (ein Los pro Gast)
- Highlight: Versteigerung zugunsten von Diversity-Projekten
**Spendenmöglichkeit:**
Wir suchen Preise für die Tombola, z.B.:
- Hotel-Übernachtungen
- Restaurant-Gutscheine
- Konzertkarten
- Shopping-Gutscheine
- Erlebnisse
- Produkte
**Gegenleistung:**
- Namensnennung bei der Tombola
- Logo auf Tombola-Losen
- Spendenquittung möglich (bei spendenbegünstigter Organisation)
**Anfrage:**
1. Könnten Sie einen Preis spenden?
2. Welchen Wert hätte die Spende?
3. Benötigen Sie eine Spendenbestätigung?
Über Ihre Zusage freuen wir uns sehr.
Mit freundlichen Grüßen
[Dein Name]
[Telefon]
[E-Mail]

View file

@ -0,0 +1,28 @@
BETREFF: Anfrage Sponsoring Diversity-Ball Wien 2026
Sehr geehrte Damen und Herren,
der Diversity-Ball Wien ist eine Benefizveranstaltung zugunsten diverser gemeinnütziger Organisationen und findet am 5. September 2026 im Wiener Rathaus statt.
**Event-Daten:**
- Datum: Samstag, 5. September 2026
- Uhrzeit: 18:00 02:00 Uhr
- Ort: Rathaus Wien, Festsaal
- Gäste: 3.500 Personen
- Budget: 750.000 €
**Sponsoring-Möglichkeiten:**
1. **Getränke-Sponsor** exklusive Lieferung aller Getränke (Sekt, Wein, Bier, Softdrinks)
2. **Haupt-Sponsor** Logo auf allen Materialien, Redezeit, reservierte Tische
3. **Medien-Partner** Berichterstattung und Live-Übertragung
4. **Preis-Sponsor** Tombola-Preise (Wertsachen, Reisen, Erlebnisse)
Gerne senden wir Ihnen ein detailliertes Sponsoring-Exposé zu.
Über Ihr Interesse freuen wir uns auf Ihre Rückmeldung.
Mit freundlichen Grüßen
Diversity-Ball Wien
office@diversityball.at

View file

@ -0,0 +1,30 @@
BETREFF: Anfrage Security-Dienstleistungen Diversity-Ball Wien
Sehr geehrte Damen und Herren,
für eine Großveranstaltung im Wiener Rathaus suchen wir einen Security-Dienstleister.
**Veranstaltungsdetails:**
- Datum: Samstag, 5. September 2026
- Uhrzeit: 18:00 02:00 Uhr (Aufbau ab 12:00 Uhr)
- Ort: Rathaus Wien, Festsaal
- Gäste: 3.500 Personen
- Format: Benefiz-Ball mit Menü, Tanz, Tombola
**Benötigte Leistungen:**
- Einlasskontrolle (ca. 8-10 Personen)
- Saal-Security während der Veranstaltung (ca. 12 Personen)
- Garderobe (ca. 4 Personen)
- VIP-Betreuung (ca. 4 Personen)
**Anforderungen:**
- Gewerberechtliche Zulassung
- Erfahrung mit Veranstaltungen >1.000 Personen
- Verfügbarkeit am 5. September 2026
Bitte um Angebot inkl. Personalstunden, Kosten und Referenzen.
Mit freundlichen Grüßen
Diversity-Ball Wien
office@diversityball.at

View file

@ -0,0 +1,28 @@
BETREFF: Anfrage Veranstaltungsversicherung Diversity-Ball Wien
Sehr geehrte Damen und Herren,
wir benötigen eine Versicherung für eine Benefiz-Großveranstaltung.
**Veranstaltungsdetails:**
- Datum: Samstag, 5. September 2026
- Uhrzeit: 18:00 02:00 Uhr
- Ort: Rathaus Wien, Festsaal
- Gäste: 3.500 Personen
- Veranstaltungsart: Benefiz-Ball
**Versicherungsbedarf:**
- Haftpflichtversicherung (Personen-/Sachschäden)
- Veranstaltungsausfall-Versicherung
- Equipment-Versicherung (Technik, Dekoration)
- Versicherung für Tombola-Preise
**Organisator:**
- Diversity-Ball Wien
- office@diversityball.at
Bitte um Angebot und Konditionen.
Mit freundlichen Grüßen
Diversity-Ball Wien

View file

@ -0,0 +1,30 @@
BETREFF: Anfrage Dekoration & Floristik Diversity-Ball Wien
Sehr geehrte Damen und Herren,
wir suchen einen Partner für Dekoration und Floristik für unseren Diversity-Ball.
**Veranstaltungsdetails:**
- Datum: Samstag, 5. September 2026
- Uhrzeit: 18:00 02:00 Uhr
- Ort: Rathaus Wien, Festsaal
- Gäste: 3.500 Personen
- Theme: Diversity & Inklusion
**Gewünschte Leistungen:**
- Tischdekoration (ca. 350 Tischplätze)
- Bühnendekoration
- Entrance-Gestaltung (Sektempfang)
- Florale Gestaltung (Blumen, Arrangements)
- Lichtkonzept (atmosphärisch)
**Zeitrahmen:**
- Aufbau: 5. September 2026, 12:00-17:00 Uhr
- Abbau: 6. September 2026, 02:00-08:00 Uhr
Bitte um Portfolio und Angebot.
Mit freundlichen Grüßen
Diversity-Ball Wien
office@diversityball.at

35
emails/UEBERSICHT.txt Normal file
View file

@ -0,0 +1,35 @@
# E-Mail-Übersicht | Diversity-Ball 5.9.2026
## Fertige E-Mails (zum Absenden)
| # | Datei | Empfänger | Betreff |
|---|-------|-----------|---------|
| 1 | `01_MA36_Genehmigung.txt` | MA 36 (Veranstaltungsbehörde) | Anfrage Veranstaltungsanmeldung |
| 2 | `02_Rathauskeller_Catering.txt` | Rathauskeller | Catering-Anfrage |
| 3 | `03_Finanzamt_Gemeinnuetzigkeit.txt` | Finanzamt | Anfrage Gemeinnützigkeitsstatus |
| 4 | `04_Technik_Anfrage.txt` | Technik-Anbieter (z.B. NUNTIO, VA.TEC) | Technische Ausstattung |
| 5 | `05_OGS_Dolmetscher.txt` | ÖGS-Dolmetscher | Barrierefreiheit |
| 6 | `06_Getraenke_Sponsor.txt` | Getränke-Lieferanten | Sponsoring Getränke |
| 7 | `07_Tombola_Preise.txt` | Wiener Unternehmen | Tombola-Preise |
| 8 | `08_Sponsoring_Anfrage.txt` | Potenzielle Sponsoren | Sponsoring-Anfrage |
| 9 | `09_Security_Anfrage.txt` | Security-Dienstleister | Security-Dienstleistungen |
| 10 | `10_Versicherung_Anfrage.txt` | Versicherungsanbieter | Veranstaltungsversicherung |
| 11 | `11_Dekoration_Anfrage.txt` | Deko/Floristik-Partner | Dekoration & Floristik |
---
## fehlende Empfänger (selbst ergänzen)
- **Rathaus Verwaltung** (Raumbuchung): [contact@wien.at]
- **Diversity Ball Wien** (Beratung): office@diversityball.at
- **Best Practice**: Monika Haider ("Ballmutter")
---
## Nächste Schritte
1. Empfänger-Adressen ergänzen
2. [Dein Name] und Kontaktdaten ersetzen
3. E-Mails absenden
Alle E-Mails liegen im Ordner `emails/`

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="png" ContentType="image/png"/><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/><Override PartName="/word/numbering.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"/><Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/><Override PartName="/word/settings.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"/><Override PartName="/word/webSettings.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml"/><Override PartName="/word/header1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"/><Override PartName="/word/footer1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"/><Override PartName="/word/fontTable.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml"/><Override PartName="/word/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/><Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/><Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/></Types>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"><Template>Normal.dotm</Template><TotalTime>0</TotalTime><Pages>3</Pages><Words>379</Words><Characters>2389</Characters><Application>Microsoft Office Word</Application><DocSecurity>0</DocSecurity><Lines>19</Lines><Paragraphs>5</Paragraphs><ScaleCrop>false</ScaleCrop><Company></Company><LinksUpToDate>false</LinksUpToDate><CharactersWithSpaces>2763</CharactersWithSpaces><SharedDoc>false</SharedDoc><HyperlinksChanged>false</HyperlinksChanged><AppVersion>16.0000</AppVersion></Properties>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><dc:title></dc:title><dc:subject></dc:subject><dc:creator>Marlene Tretton</dc:creator><cp:keywords></cp:keywords><dc:description></dc:description><cp:lastModifiedBy>Marlene Tretton</cp:lastModifiedBy><cp:revision>2</cp:revision><dcterms:created xsi:type="dcterms:W3CDTF">2026-02-11T12:02:00Z</dcterms:created><dcterms:modified xsi:type="dcterms:W3CDTF">2026-02-11T12:06:00Z</dcterms:modified></cp:coreProperties>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId8" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/><Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/><Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering" Target="numbering.xml"/><Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" Target="footer1.xml"/><Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header1.xml"/><Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings" Target="webSettings.xml"/></Relationships>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="http://www.diversityball.at" TargetMode="External"/><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="mailto:office@diversityball.at" TargetMode="External"/></Relationships>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.png"/></Relationships>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:fonts xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16du"><w:font w:name="Symbol"><w:panose1 w:val="05050102010706020507"/><w:charset w:val="02"/><w:family w:val="decorative"/><w:pitch w:val="variable"/><w:sig w:usb0="00000003" w:usb1="10000000" w:usb2="00000000" w:usb3="00000000" w:csb0="80000001" w:csb1="00000000"/></w:font><w:font w:name="Times New Roman"><w:panose1 w:val="02020603050405020304"/><w:charset w:val="00"/><w:family w:val="roman"/><w:pitch w:val="variable"/><w:sig w:usb0="E0002EFF" w:usb1="C000785B" w:usb2="00000009" w:usb3="00000000" w:csb0="000001FF" w:csb1="00000000"/></w:font><w:font w:name="Courier New"><w:panose1 w:val="02070309020205020404"/><w:charset w:val="00"/><w:family w:val="modern"/><w:pitch w:val="fixed"/><w:sig w:usb0="E0002EFF" w:usb1="C0007843" w:usb2="00000009" w:usb3="00000000" w:csb0="000001FF" w:csb1="00000000"/></w:font><w:font w:name="Wingdings"><w:panose1 w:val="05000000000000000000"/><w:charset w:val="4D"/><w:family w:val="decorative"/><w:pitch w:val="variable"/><w:sig w:usb0="00000003" w:usb1="00000000" w:usb2="00000000" w:usb3="00000000" w:csb0="80000001" w:csb1="00000000"/></w:font><w:font w:name="Aptos"><w:panose1 w:val="020B0004020202020204"/><w:charset w:val="00"/><w:family w:val="swiss"/><w:pitch w:val="variable"/><w:sig w:usb0="20000287" w:usb1="00000003" w:usb2="00000000" w:usb3="00000000" w:csb0="0000019F" w:csb1="00000000"/></w:font><w:font w:name="Aptos Display"><w:panose1 w:val="020B0004020202020204"/><w:charset w:val="00"/><w:family w:val="swiss"/><w:pitch w:val="variable"/><w:sig w:usb0="20000287" w:usb1="00000003" w:usb2="00000000" w:usb3="00000000" w:csb0="0000019F" w:csb1="00000000"/></w:font><w:font w:name="Calibri"><w:panose1 w:val="020F0502020204030204"/><w:charset w:val="00"/><w:family w:val="swiss"/><w:pitch w:val="variable"/><w:sig w:usb0="E0002AFF" w:usb1="C000ACFF" w:usb2="00000009" w:usb3="00000000" w:csb0="000001FF" w:csb1="00000000"/></w:font><w:font w:name="Calibri Light"><w:panose1 w:val="020F0302020204030204"/><w:charset w:val="00"/><w:family w:val="swiss"/><w:pitch w:val="variable"/><w:sig w:usb0="E4002EFF" w:usb1="C000247B" w:usb2="00000009" w:usb3="00000000" w:csb0="000001FF" w:csb1="00000000"/></w:font><w:font w:name="Bahnschrift Light"><w:panose1 w:val="020B0502040204020203"/><w:charset w:val="00"/><w:family w:val="swiss"/><w:pitch w:val="variable"/><w:sig w:usb0="A00002C7" w:usb1="00000002" w:usb2="00000000" w:usb3="00000000" w:csb0="0000019F" w:csb1="00000000"/></w:font><w:font w:name="Arial"><w:panose1 w:val="020B0604020202020204"/><w:charset w:val="00"/><w:family w:val="swiss"/><w:pitch w:val="variable"/><w:sig w:usb0="E0002AFF" w:usb1="C0007843" w:usb2="00000009" w:usb3="00000000" w:csb0="000001FF" w:csb1="00000000"/></w:font><w:font w:name="Tahoma"><w:panose1 w:val="020B0604030504040204"/><w:charset w:val="00"/><w:family w:val="swiss"/><w:pitch w:val="variable"/><w:sig w:usb0="E1002EFF" w:usb1="C000605B" w:usb2="00000029" w:usb3="00000000" w:csb0="000101FF" w:csb1="00000000"/></w:font></w:fonts>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:hdr xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16du wp14"><w:p w14:paraId="1336E7C2" w14:textId="77777777" w:rsidR="00000000" w:rsidRDefault="00000000" w:rsidP="00BF0AFA"><w:pPr><w:pStyle w:val="Kopfzeile"/><w:tabs><w:tab w:val="clear" w:pos="9072"/><w:tab w:val="right" w:pos="9046"/></w:tabs><w:jc w:val="center"/></w:pPr><w:r><w:rPr><w:noProof/><w:lang w:val="de-AT" w:eastAsia="de-AT"/></w:rPr><w:drawing><wp:inline distT="0" distB="0" distL="0" distR="0" wp14:anchorId="3D6CEEE3" wp14:editId="17154911"><wp:extent cx="952269" cy="1078524"/><wp:effectExtent l="0" t="0" r="635" b="7620"/><wp:docPr id="3" name="Grafik 3"/><wp:cNvGraphicFramePr><a:graphicFrameLocks xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" noChangeAspect="1"/></wp:cNvGraphicFramePr><a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:nvPicPr><pic:cNvPr id="3" name="DB-bunt-transoarent-Screen.png"/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed="rId1"><a:extLst><a:ext uri="{28A0092B-C50C-407E-A947-70E740481C1C}"><a14:useLocalDpi xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" val="0"/></a:ext></a:extLst></a:blip><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="955546" cy="1082236"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r></w:p></w:hdr>

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 KiB

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:settings xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16du"><w:zoom w:percent="228"/><w:proofState w:spelling="clean" w:grammar="clean"/><w:defaultTabStop w:val="708"/><w:hyphenationZone w:val="425"/><w:characterSpacingControl w:val="doNotCompress"/><w:compat><w:compatSetting w:name="compatibilityMode" w:uri="http://schemas.microsoft.com/office/word" w:val="15"/><w:compatSetting w:name="overrideTableStyleFontSizeAndJustification" w:uri="http://schemas.microsoft.com/office/word" w:val="1"/><w:compatSetting w:name="enableOpenTypeFeatures" w:uri="http://schemas.microsoft.com/office/word" w:val="1"/><w:compatSetting w:name="doNotFlipMirrorIndents" w:uri="http://schemas.microsoft.com/office/word" w:val="1"/><w:compatSetting w:name="differentiateMultirowTableHeaders" w:uri="http://schemas.microsoft.com/office/word" w:val="1"/><w:compatSetting w:name="useWord2013TrackBottomHyphenation" w:uri="http://schemas.microsoft.com/office/word" w:val="0"/></w:compat><w:rsids><w:rsidRoot w:val="00393CB6"/><w:rsid w:val="00393CB6"/><w:rsid w:val="00CB4F65"/><w:rsid w:val="00FC6E38"/></w:rsids><m:mathPr><m:mathFont m:val="Cambria Math"/><m:brkBin m:val="before"/><m:brkBinSub m:val="--"/><m:smallFrac m:val="0"/><m:dispDef/><m:lMargin m:val="0"/><m:rMargin m:val="0"/><m:defJc m:val="centerGroup"/><m:wrapIndent m:val="1440"/><m:intLim m:val="subSup"/><m:naryLim m:val="undOvr"/></m:mathPr><w:themeFontLang w:val="de-AT"/><w:clrSchemeMapping w:bg1="light1" w:t1="dark1" w:bg2="light2" w:t2="dark2" w:accent1="accent1" w:accent2="accent2" w:accent3="accent3" w:accent4="accent4" w:accent5="accent5" w:accent6="accent6" w:hyperlink="hyperlink" w:followedHyperlink="followedHyperlink"/><w:decimalSymbol w:val=","/><w:listSeparator w:val=";"/><w14:docId w14:val="0DB37833"/><w15:chartTrackingRefBased/><w15:docId w15:val="{CEF95732-EDB6-D541-A49C-A0B68BB8D359}"/></w:settings>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:webSettings xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16du"><w:optimizeForBrowser/><w:allowPNG/></w:webSettings>

720
static/style.css Normal file
View file

@ -0,0 +1,720 @@
/* ── Frankenbot Dark Theme v2 ──────────────────────────────────────────────── */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
:root {
--bg-base: #080b12;
--bg-surface: #0f1320;
--bg-elevated: #181d2e;
--bg-hover: #1f2640;
--bg-glass: rgba(15,19,32,.7);
--accent: #7c6fff;
--accent-light: #a89eff;
--accent-dim: #4a45a0;
--accent-glow: rgba(124,111,255,.3);
--accent2: #06d6a0;
--accent2-glow: rgba(6,214,160,.25);
--success: #06d6a0;
--warning: #fbbf24;
--danger: #f43f5e;
--info: #38bdf8;
--text-primary: #e8eaf6;
--text-secondary: #94a3b8;
--text-muted: #4e5a72;
--border: rgba(255,255,255,.07);
--border-accent: rgba(124,111,255,.35);
--shadow: 0 8px 32px rgba(0,0,0,.5);
--shadow-sm: 0 2px 12px rgba(0,0,0,.3);
--radius: 14px;
--radius-sm: 9px;
--radius-xs: 6px;
--transition: .18s cubic-bezier(.4,0,.2,1);
}
/* ── Reset & Base ─────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
min-height: 100vh;
background: var(--bg-base);
background-image:
radial-gradient(ellipse 80% 50% at 50% -10%, rgba(124,111,255,.12) 0%, transparent 60%),
radial-gradient(ellipse 40% 30% at 90% 80%, rgba(6,214,160,.06) 0%, transparent 50%);
color: var(--text-primary);
font-family: 'Inter', 'Segoe UI', system-ui, sans-serif;
font-size: .9375rem;
line-height: 1.65;
-webkit-font-smoothing: antialiased;
}
/* ── Scrollbar ────────────────────────────────────────────────────────────── */
::-webkit-scrollbar { width: 5px; height: 5px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--bg-hover); border-radius: 99px; }
::-webkit-scrollbar-thumb:hover { background: var(--accent-dim); }
/* ── Navbar ───────────────────────────────────────────────────────────────── */
.navbar {
background: var(--bg-glass) !important;
border-bottom: 1px solid var(--border);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
padding: .6rem 0;
position: sticky;
top: 0;
z-index: 1000;
}
.navbar-brand {
font-weight: 700;
font-size: 1rem;
color: var(--text-primary) !important;
letter-spacing: -.02em;
display: flex;
align-items: center;
gap: .5rem;
text-decoration: none;
}
.navbar-brand .brand-icon {
width: 28px;
height: 28px;
background: linear-gradient(135deg, var(--accent), var(--accent2));
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: .75rem;
box-shadow: 0 0 16px var(--accent-glow);
animation: brand-pulse 3s ease-in-out infinite;
}
@keyframes brand-pulse {
0%, 100% { box-shadow: 0 0 16px var(--accent-glow); }
50% { box-shadow: 0 0 28px var(--accent-glow), 0 0 8px var(--accent2-glow); }
}
.nav-link {
color: var(--text-muted) !important;
font-size: .8125rem;
font-weight: 500;
padding: .4rem .7rem !important;
border-radius: var(--radius-xs);
transition: color var(--transition), background var(--transition);
letter-spacing: .01em;
display: flex;
align-items: center;
gap: .35rem;
white-space: nowrap;
}
.nav-link:hover,
.nav-link.active {
color: var(--text-primary) !important;
background: rgba(124,111,255,.12);
}
.nav-link.active {
color: var(--accent-light) !important;
background: rgba(124,111,255,.18);
}
.navbar-toggler {
border-color: var(--border);
padding: .3rem .5rem;
}
.navbar-toggler-icon { filter: invert(.7); }
/* ── Page Header ──────────────────────────────────────────────────────────── */
.page-header {
padding: 1.75rem 0 1.25rem;
margin-bottom: 1.75rem;
border-bottom: 1px solid var(--border);
position: relative;
}
.page-header::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
width: 60px;
height: 2px;
background: linear-gradient(90deg, var(--accent), transparent);
border-radius: 99px;
}
.page-header h1 {
font-size: 1.5rem;
font-weight: 700;
letter-spacing: -.03em;
margin-bottom: .2rem;
background: linear-gradient(135deg, var(--text-primary), var(--text-secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.page-header p {
color: var(--text-muted);
font-size: .8125rem;
margin: 0;
}
/* ── Cards ────────────────────────────────────────────────────────────────── */
.card {
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow-sm);
color: var(--text-primary);
transition: border-color var(--transition), box-shadow var(--transition), transform var(--transition);
overflow: hidden;
}
.card:hover {
border-color: rgba(124,111,255,.2);
box-shadow: var(--shadow);
}
.card-header {
background: var(--bg-elevated) !important;
border-bottom: 1px solid var(--border) !important;
color: var(--text-primary) !important;
padding: .85rem 1.25rem;
font-weight: 600;
font-size: .9rem;
}
.card-header.bg-primary { background: linear-gradient(135deg, rgba(79,70,229,.8), rgba(124,111,255,.8)) !important; border-bottom-color: rgba(124,111,255,.3) !important; }
.card-header.bg-success { background: linear-gradient(135deg, rgba(5,150,105,.8), rgba(6,214,160,.8)) !important; border-bottom-color: rgba(6,214,160,.3) !important; color: #fff !important; }
.card-header.bg-dark { background: linear-gradient(135deg, #10141f, #1a2035) !important; }
.card-header.bg-warning { background: linear-gradient(135deg, rgba(180,100,0,.85), rgba(251,191,36,.85)) !important; color: #fff !important; border-bottom-color: rgba(251,191,36,.3) !important; }
.card-header.bg-info { background: linear-gradient(135deg, rgba(2,132,199,.8), rgba(56,189,248,.8)) !important; border-bottom-color: rgba(56,189,248,.3) !important; }
.card-header.bg-secondary{ background: linear-gradient(135deg, #1e2640, #242e48) !important; }
.card-header.bg-danger { background: linear-gradient(135deg, rgba(185,28,28,.8), rgba(244,63,94,.8)) !important; border-bottom-color: rgba(244,63,94,.3) !important; }
.card-body { padding: 1.25rem; }
.card-footer {
background: var(--bg-elevated) !important;
border-top: 1px solid var(--border) !important;
padding: .65rem 1.25rem;
}
/* Agent cards */
.agent-card {
transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition);
cursor: default;
}
.agent-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 40px rgba(124,111,255,.2);
border-color: var(--accent);
}
/* ── Stat Cards ───────────────────────────────────────────────────────────── */
.stat-card {
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1.5rem 1.25rem;
text-align: center;
transition: border-color var(--transition), transform var(--transition), box-shadow var(--transition);
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 2px;
background: linear-gradient(90deg, var(--accent), var(--accent2));
opacity: 0;
transition: opacity var(--transition);
}
.stat-card:hover {
border-color: var(--border-accent);
transform: translateY(-3px);
box-shadow: 0 8px 32px rgba(124,111,255,.15);
}
.stat-card:hover::before { opacity: 1; }
.stat-card .stat-number {
font-size: 2.2rem;
font-weight: 700;
background: linear-gradient(135deg, var(--accent-light), var(--accent2));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
line-height: 1;
margin-bottom: .4rem;
}
.stat-card .stat-label {
font-size: .7rem;
text-transform: uppercase;
letter-spacing: .1em;
color: var(--text-muted);
font-weight: 600;
}
/* ── Buttons ──────────────────────────────────────────────────────────────── */
.btn {
border-radius: var(--radius-sm);
font-weight: 500;
font-size: .8125rem;
padding: .45rem 1rem;
transition: all var(--transition);
border: none;
display: inline-flex;
align-items: center;
gap: .35rem;
letter-spacing: .01em;
cursor: pointer;
text-decoration: none;
}
.btn-primary {
background: linear-gradient(135deg, var(--accent), #6058d0);
color: #fff;
box-shadow: 0 2px 12px rgba(124,111,255,.3);
}
.btn-primary:hover {
background: linear-gradient(135deg, var(--accent-light), var(--accent));
box-shadow: 0 4px 20px rgba(124,111,255,.45);
transform: translateY(-1px);
color: #fff;
}
.btn-success {
background: linear-gradient(135deg, #059669, var(--success));
color: #fff;
box-shadow: 0 2px 12px rgba(6,214,160,.25);
}
.btn-success:hover {
background: linear-gradient(135deg, #06d6a0, #059669);
box-shadow: 0 4px 20px rgba(6,214,160,.4);
transform: translateY(-1px);
color: #fff;
}
.btn-danger {
background: linear-gradient(135deg, #be123c, var(--danger));
color: #fff;
}
.btn-danger:hover {
background: linear-gradient(135deg, var(--danger), #be123c);
transform: translateY(-1px);
color: #fff;
}
.btn-secondary {
background: var(--bg-elevated);
color: var(--text-secondary);
border: 1px solid var(--border);
}
.btn-secondary:hover {
background: var(--bg-hover);
color: var(--text-primary);
border-color: rgba(124,111,255,.3);
}
.btn-info {
background: linear-gradient(135deg, #0284c7, var(--info));
color: #fff;
}
.btn-info:hover {
background: linear-gradient(135deg, var(--info), #0284c7);
transform: translateY(-1px);
color: #fff;
}
.btn-warning {
background: linear-gradient(135deg, #d97706, var(--warning));
color: #0f1117;
}
.btn-warning:hover {
background: linear-gradient(135deg, var(--warning), #d97706);
transform: translateY(-1px);
}
.btn-outline-primary {
background: transparent;
border: 1px solid var(--accent-dim);
color: var(--accent-light);
}
.btn-outline-primary:hover {
background: rgba(124,111,255,.15);
border-color: var(--accent);
color: var(--accent-light);
}
.btn-outline-secondary {
background: transparent;
border: 1px solid var(--border);
color: var(--text-muted);
}
.btn-outline-secondary:hover {
background: var(--bg-hover);
color: var(--text-primary);
border-color: var(--text-muted);
}
.btn-sm { padding: .28rem .65rem; font-size: .76rem; border-radius: var(--radius-xs); }
.btn-lg { padding: .65rem 1.5rem; font-size: .9375rem; }
.btn:disabled { opacity: .5; cursor: not-allowed; transform: none !important; }
/* ── Forms ────────────────────────────────────────────────────────────────── */
.form-control,
.form-select {
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text-primary);
padding: .55rem .9rem;
font-size: .875rem;
transition: border-color var(--transition), box-shadow var(--transition);
}
.form-control:focus,
.form-select:focus {
background: var(--bg-elevated);
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-glow);
color: var(--text-primary);
outline: none;
}
.form-control::placeholder { color: var(--text-muted); }
.form-label {
color: var(--text-secondary);
font-size: .76rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .07em;
margin-bottom: .4rem;
}
.form-select option { background: var(--bg-elevated); }
textarea.form-control { resize: vertical; min-height: 80px; }
/* ── Tables ───────────────────────────────────────────────────────────────── */
.table {
color: var(--text-primary);
margin-bottom: 0;
border-collapse: separate;
border-spacing: 0;
}
.table th {
border-top: none;
border-bottom: 1px solid var(--border);
font-weight: 600;
font-size: .72rem;
text-transform: uppercase;
letter-spacing: .08em;
color: var(--text-muted);
padding: .75rem 1rem;
background: rgba(0,0,0,.2);
}
.table td {
border-bottom: 1px solid var(--border);
padding: .75rem 1rem;
vertical-align: middle;
color: var(--text-secondary);
font-size: .875rem;
}
.table tbody tr:last-child td { border-bottom: none; }
.table tbody tr:hover td {
background: var(--bg-hover);
color: var(--text-primary);
}
.table-striped > tbody > tr:nth-of-type(odd) > * {
background: rgba(255,255,255,.015);
}
/* ── Badges ───────────────────────────────────────────────────────────────── */
.badge {
padding: .28em .65em;
border-radius: 5px;
font-size: .68rem;
font-weight: 600;
letter-spacing: .05em;
text-transform: uppercase;
}
.bg-primary { background: rgba(124,111,255,.25) !important; color: var(--accent-light) !important; border: 1px solid rgba(124,111,255,.3); }
.bg-success { background: rgba(6,214,160,.2) !important; color: #34d399 !important; border: 1px solid rgba(6,214,160,.3); }
.bg-warning { background: rgba(251,191,36,.2) !important; color: #fbbf24 !important; border: 1px solid rgba(251,191,36,.3); }
.bg-danger { background: rgba(244,63,94,.2) !important; color: #f87171 !important; border: 1px solid rgba(244,63,94,.3); }
.bg-info { background: rgba(56,189,248,.2) !important; color: #38bdf8 !important; border: 1px solid rgba(56,189,248,.3); }
.bg-secondary { background: var(--bg-elevated) !important; color: var(--text-muted) !important; border: 1px solid var(--border); }
.bg-dark { background: rgba(0,0,0,.4) !important; color: var(--text-secondary) !important; border: 1px solid var(--border); }
/* ── Alerts ───────────────────────────────────────────────────────────────── */
.alert {
border-radius: var(--radius-sm);
border: 1px solid;
font-size: .875rem;
padding: .9rem 1.1rem;
backdrop-filter: blur(8px);
}
.alert-success { background: rgba(6,214,160,.08); border-color: rgba(6,214,160,.25); color: #34d399; }
.alert-danger { background: rgba(244,63,94,.08); border-color: rgba(244,63,94,.25); color: #f87171; }
.alert-warning { background: rgba(251,191,36,.08); border-color: rgba(251,191,36,.25); color: #fbbf24; }
.alert-info { background: rgba(56,189,248,.08); border-color: rgba(56,189,248,.25); color: #7dd3fc; }
.alert-dismissible .btn-close { filter: invert(1) opacity(.5); }
/* ── Chat ─────────────────────────────────────────────────────────────────── */
.chat-container {
max-height: 560px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 1rem;
padding: .5rem 0;
}
.chat-message {
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 1rem 1.1rem;
border-left: 3px solid var(--accent);
transition: border-color var(--transition);
}
.chat-message:hover { border-left-color: var(--accent2); }
.chat-prompt {
background: rgba(0,0,0,.25);
border-radius: var(--radius-xs);
padding: .55rem .85rem;
margin-bottom: .6rem;
color: var(--text-primary);
font-size: .875rem;
border-left: 2px solid var(--text-muted);
}
.chat-response {
background: linear-gradient(135deg, rgba(124,111,255,.1), rgba(6,214,160,.05));
border: 1px solid rgba(124,111,255,.2);
border-radius: var(--radius-xs);
padding: .65rem .85rem;
color: var(--text-primary);
font-size: .875rem;
white-space: pre-wrap;
line-height: 1.7;
}
.chat-timestamp {
color: var(--text-muted);
font-size: .72rem;
margin-bottom: .5rem;
display: flex;
align-items: center;
gap: .5rem;
}
/* ── List Group ───────────────────────────────────────────────────────────── */
.list-group-item {
background: var(--bg-surface);
border: none;
border-bottom: 1px solid var(--border);
color: var(--text-primary);
padding: .85rem 1.1rem;
transition: background var(--transition);
}
.list-group-item:last-child { border-bottom: none; }
.list-group-item-action:hover { background: var(--bg-hover); color: var(--text-primary); }
.list-group-flush .list-group-item { border-radius: 0 !important; }
/* ── Modal ────────────────────────────────────────────────────────────────── */
.modal-content {
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--text-primary);
box-shadow: 0 24px 80px rgba(0,0,0,.6);
}
.modal-header {
border-bottom: 1px solid var(--border);
background: var(--bg-elevated);
border-radius: var(--radius) var(--radius) 0 0;
padding: 1rem 1.25rem;
}
.modal-footer {
border-top: 1px solid var(--border);
background: var(--bg-elevated);
border-radius: 0 0 var(--radius) var(--radius);
}
.modal-backdrop { backdrop-filter: blur(4px); }
.btn-close { filter: invert(1) opacity(.6); }
/* ── Footer ───────────────────────────────────────────────────────────────── */
footer {
margin-top: 60px;
padding: 18px 0;
background: var(--bg-surface);
border-top: 1px solid var(--border);
text-align: center;
color: var(--text-muted);
font-size: .76rem;
}
/* ── File Items ───────────────────────────────────────────────────────────── */
.file-item {
display: flex;
align-items: center;
gap: .7rem;
padding: .7rem .9rem;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
background: var(--bg-elevated);
transition: background var(--transition), border-color var(--transition), transform var(--transition);
margin-bottom: .5rem;
}
.file-item:last-child { margin-bottom: 0; }
.file-item:hover { background: var(--bg-hover); border-color: rgba(124,111,255,.25); transform: translateX(2px); }
.file-icon { font-size: 1.1rem; flex-shrink: 0; }
.file-name { flex: 1; font-size: .875rem; color: var(--text-primary); font-weight: 500; word-break: break-all; }
.file-meta { font-size: .72rem; color: var(--text-muted); white-space: nowrap; }
.file-actions { display: flex; gap: .35rem; flex-shrink: 0; }
/* ── Status / Email Log ───────────────────────────────────────────────────── */
.status-replied { color: var(--success); font-weight: 600; }
.status-skipped { color: var(--text-muted); }
.status-error { color: var(--danger); font-weight: 600; }
.badge-agent { font-size: .68rem; }
.log-table td { vertical-align: middle; }
.response-preview {
font-size: .8rem;
color: var(--text-secondary);
white-space: pre-wrap;
word-break: break-word;
max-height: 70px;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
/* ── Typography ───────────────────────────────────────────────────────────── */
h1, h2, h3, h4 { letter-spacing: -.02em; }
h1 { font-size: 1.5rem; font-weight: 700; }
h3 { font-size: 1rem; font-weight: 600; color: var(--text-secondary); }
/* ── Misc Utilities ───────────────────────────────────────────────────────── */
.text-muted { color: var(--text-muted) !important; }
.text-secondary{ color: var(--text-secondary) !important; }
.bg-light { background: var(--bg-elevated) !important; color: var(--text-primary) !important; }
pre, code { background: var(--bg-elevated); color: #a5f3fc; border-radius: var(--radius-xs); padding: .15em .45em; font-family: 'JetBrains Mono', monospace; font-size: .82em; }
pre { padding: 1rem 1.1rem; overflow-x: auto; line-height: 1.6; }
hr { border-color: var(--border); opacity: 1; }
.font-monospace { font-family: 'JetBrains Mono', 'Fira Code', monospace !important; font-size: .83rem !important; }
/* ── Live streaming indicator ─────────────────────────────────────────────── */
.streaming-dot {
display: inline-block;
width: 7px; height: 7px;
border-radius: 50%;
background: var(--accent2);
animation: blink 1s infinite;
margin-left: .4rem;
vertical-align: middle;
}
@keyframes blink { 0%,100%{opacity:1;} 50%{opacity:.2;} }
/* ── Progress / skeleton ──────────────────────────────────────────────────── */
.skeleton {
background: linear-gradient(90deg, var(--bg-elevated) 25%, var(--bg-hover) 50%, var(--bg-elevated) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: var(--radius-xs);
}
@keyframes shimmer { 0%{background-position:200% 0;} 100%{background-position:-200% 0;} }
/* ── Inline editor ────────────────────────────────────────────────────────── */
.inline-editor {
width: 100%;
min-height: 320px;
background: var(--bg-base);
border: 1px solid var(--border-accent);
border-radius: var(--radius-sm);
color: #a5f3fc;
font-family: 'JetBrains Mono', monospace;
font-size: .83rem;
padding: 1rem;
line-height: 1.6;
resize: vertical;
transition: border-color var(--transition), box-shadow var(--transition);
}
.inline-editor:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-glow);
}
/* ── Hero section ─────────────────────────────────────────────────────────── */
.hero-section {
background: linear-gradient(135deg, rgba(124,111,255,.1), rgba(6,214,160,.05));
border: 1px solid rgba(124,111,255,.15);
border-radius: var(--radius);
padding: 2rem;
margin-bottom: 1.75rem;
position: relative;
overflow: hidden;
}
.hero-section::before {
content: '';
position: absolute;
top: -50%; right: -20%;
width: 300px; height: 300px;
border-radius: 50%;
background: radial-gradient(circle, rgba(124,111,255,.08) 0%, transparent 70%);
pointer-events: none;
}
/* ── Responsive adjustments ───────────────────────────────────────────────── */
@media (max-width: 768px) {
.page-header { padding: 1.25rem 0 1rem; }
.hero-section { padding: 1.25rem; }
.stat-card .stat-number { font-size: 1.75rem; }
.file-actions { flex-direction: column; }
}

75
templates/agents.html Normal file
View file

@ -0,0 +1,75 @@
{% extends "base.html" %}
{% block title %}Agenten{% endblock %}
{% block content %}
<div class="page-header">
<h1>Agenten-Verwaltung</h1>
<p>System-Prompts bearbeiten und verwalten</p>
</div>
{% if edit_agent %}
<div class="card">
<div class="card-header bg-warning">
<h5 class="mb-0">Prompt bearbeiten: {{ edit_agent }}</h5>
</div>
<div class="card-body">
<form method="POST" action="{{ url_for('agents') }}">
<input type="hidden" name="agent_name" value="{{ edit_agent }}">
<div class="mb-3">
<label for="prompt_content" class="form-label">System-Prompt</label>
<textarea class="form-control font-monospace" id="prompt_content" name="prompt_content"
rows="22" style="font-size:.82rem;line-height:1.5;">{{ edit_prompt }}</textarea>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Speichern</button>
<a href="{{ url_for('agents') }}" class="btn btn-secondary">Abbrechen</a>
</div>
</form>
</div>
</div>
{% else %}
<div class="card">
<div class="card-header bg-dark d-flex justify-content-between align-items-center">
<h5 class="mb-0">Alle Agenten</h5>
<span class="badge bg-secondary">{{ agents_list|length }}</span>
</div>
<div class="card-body p-0">
{% if agents_list %}
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>Agent</th>
<th>Prompt-Vorschau</th>
<th style="width:120px;">Aktionen</th>
</tr>
</thead>
<tbody>
{% for agent in agents_list %}
<tr>
<td>
<strong>{{ agent.name }}</strong>
</td>
<td>
<small style="color:var(--text-muted);">
{{ agent.prompt[:160] }}{% if agent.prompt|length > 160 %}…{% endif %}
</small>
</td>
<td>
<a href="{{ url_for('agents', edit=agent.name) }}" class="btn btn-sm btn-outline-primary">Bearbeiten</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-warning m-3">
Keine Agenten gefunden. Stelle sicher, dass <code>agents/</code> Unterverzeichnisse mit <code>systemprompt.md</code> enthält.
</div>
{% endif %}
</div>
</div>
{% endif %}
{% endblock %}

102
templates/base.html Normal file
View file

@ -0,0 +1,102 @@
<!DOCTYPE html>
<html lang="de" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Frankenbot{% endblock %} · Frankenbot</title>
<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.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 rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
{% block head %}{% endblock %}
</head>
<body>
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand" href="/">
<span class="brand-icon"></span>
Frankenbot
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto gap-1">
<li class="nav-item">
<a class="nav-link {% if request.path == '/' %}active{% endif %}" href="/">
<span></span> Dashboard
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path == '/chat' %}active{% endif %}" href="/chat">
<span>💬</span> Chat
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path == '/orchestrator' %}active{% endif %}" href="/orchestrator">
<span>🤖</span> Orchestrator
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path == '/tasks' %}active{% endif %}" href="/tasks">
<span></span> Tasks
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path == '/files' %}active{% endif %}" href="/files">
<span>📂</span> Dateien
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path == '/agents' %}active{% endif %}" href="/agents">
<span></span> Agenten
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path == '/emails' %}active{% endif %}" href="/emails">
<span></span> Emails
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path == '/email-log' %}active{% endif %}" href="/email-log">
<span>📋</span> Log
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path == '/settings' %}active{% endif %}" href="/settings">
<span></span> Settings
</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container py-4">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show mb-3" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
<footer>
<div class="container">
Frankenbot &middot; Diversity-Ball Wien 2026 &middot;
<span style="color:var(--accent);">Agent Orchestration System</span>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
{% block scripts %}{% endblock %}
</body>
</html>

70
templates/chat.html Normal file
View file

@ -0,0 +1,70 @@
{% extends "base.html" %}
{% block title %}Chat{% endblock %}
{% block content %}
<div class="page-header">
<h1>Agenten Chat</h1>
<p>Direkte Anfrage an einen spezifischen Agenten</p>
</div>
<div class="row g-4">
<div class="col-lg-4">
<div class="card">
<div class="card-header bg-primary">
<span>💬 Neue Anfrage</span>
</div>
<div class="card-body">
<form method="POST" action="/chat">
<div class="mb-3">
<label for="agent" class="form-label">Agent</label>
<select class="form-select" id="agent" name="agent" required>
<option value="">— Agent wählen —</option>
{% for key, agent in agents.items() %}
<option value="{{ key }}">{{ agent.name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="prompt" class="form-label">Ihre Anfrage</label>
<textarea class="form-control" id="prompt" name="prompt" rows="5"
placeholder="Was soll der Agent erledigen?" required></textarea>
</div>
<button type="submit" class="btn btn-primary w-100">Absenden</button>
</form>
</div>
</div>
</div>
<div class="col-lg-8">
<div class="card">
<div class="card-header bg-secondary d-flex align-items-center justify-content-between">
<span>Chat-Verlauf</span>
{% if chat_history %}
<span class="badge bg-secondary">{{ chat_history|length }}</span>
{% endif %}
</div>
<div class="card-body chat-container" id="chatContainer">
{% if chat_history %}
{% for chat in chat_history %}
<div class="chat-message">
<div class="chat-timestamp">
{{ chat.timestamp }}
<span class="badge bg-primary">{{ chat.agent }}</span>
</div>
<div class="chat-prompt"><strong>Sie:</strong> {{ chat.prompt }}</div>
<div class="chat-response mt-1"><strong>Antwort:</strong> {{ chat.response }}</div>
</div>
{% endfor %}
{% else %}
<div class="text-center py-5" style="color:var(--text-muted);">
<div style="font-size:2.5rem;margin-bottom:.75rem;">💬</div>
<p style="margin:0;">Noch keine Nachrichten.<br>
<small>Starten Sie eine Konversation.</small>
</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

90
templates/email_log.html Normal file
View file

@ -0,0 +1,90 @@
{% extends "base.html" %}
{% block title %}Email-Log{% endblock %}
{% block content %}
<div class="page-header d-flex align-items-center justify-content-between">
<div>
<h1>Email-Verarbeitungs-Log</h1>
<p>Automatisch verarbeitete Emails und Antworten</p>
</div>
<div class="d-flex gap-2 align-items-center">
<span class="badge bg-secondary" style="font-size:.85rem;">{{ log_entries|length }} Einträge</span>
<button class="btn btn-secondary btn-sm" onclick="location.reload()">Aktualisieren</button>
</div>
</div>
<div class="mb-3" style="font-size:.8rem;color:var(--text-muted);">
<span class="status-replied me-3">✓ replied</span> — Auto-Reply versendet &nbsp;|&nbsp;
<span class="status-skipped me-3">— skipped</span> — Nicht auf Whitelist &nbsp;|&nbsp;
<span class="status-error">✗ error</span> — Fehler beim Versenden
</div>
{% if not log_entries %}
<div class="card">
<div class="card-body text-center py-5" style="color:var(--text-muted);">
<p style="font-size:2rem;">📭</p>
<p>Noch keine Emails verarbeitet.<br><small>Der Poller prüft alle 2 Minuten den Posteingang.</small></p>
</div>
</div>
{% else %}
<div class="card">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover log-table mb-0">
<thead>
<tr>
<th>Zeitstempel</th>
<th>Von</th>
<th>Betreff</th>
<th>Agent</th>
<th>Status</th>
<th>Antwort-Vorschau</th>
</tr>
</thead>
<tbody>
{% for entry in log_entries %}
<tr>
<td style="white-space:nowrap;"><small style="color:var(--text-muted);">{{ entry.timestamp }}</small></td>
<td><small>{{ entry.from }}</small></td>
<td>{{ entry.subject }}</td>
<td>
{% if entry.agent %}
<span class="badge bg-primary badge-agent">{{ entry.agent }}</span>
{% else %}
<span style="color:var(--text-muted);"></span>
{% endif %}
</td>
<td>
{% if entry.status == 'replied' or entry.status == 'completed' %}
<span class="status-replied">✓ replied</span>
{% elif entry.status == 'skipped' %}
<span class="status-skipped">— skipped</span>
{% elif entry.status == 'error' %}
<span class="status-error">✗ error</span>
{% elif entry.status == 'queued' %}
<span style="color:var(--warning);">⏳ queued</span>
{% else %}
<span style="color:var(--text-muted);">{{ entry.status }}</span>
{% endif %}
</td>
<td>
{% if entry.response_preview %}
<div class="response-preview">{{ entry.response_preview }}</div>
{% else %}
<small style="color:var(--text-muted);"></small>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
<div class="mt-3" style="font-size:.78rem;color:var(--text-muted);">
Whitelist: <strong>eric.fischer@signtime.media</strong>, <strong>p.dyderski@live.at</strong>,
<strong>georg.tschare@gmail.com</strong>, <strong>*@diversityball.at</strong> · Max. 50 Einträge
</div>
{% endblock %}

123
templates/emails.html Normal file
View file

@ -0,0 +1,123 @@
{% extends "base.html" %}
{% block title %}Emails{% endblock %}
{% block content %}
<div class="page-header">
<h1>Email-Verwaltung</h1>
<p>Posteingang und Email-Versand</p>
</div>
{% if not email_config_valid %}
<div class="alert alert-warning">
<strong>Konfiguration erforderlich</strong><br>
<small>Setze <code>IMAP_SERVER</code>, <code>SMTP_SERVER</code>, <code>EMAIL_ADDRESS</code>, <code>EMAIL_PASSWORD</code> in der <code>.env</code>-Datei.</small>
</div>
{% else %}
<div class="alert alert-success">
<strong>Verbunden</strong> · {{ current_email }}
</div>
{% endif %}
<div class="row g-4">
<!-- Neue Email -->
<div class="col-lg-4">
<div class="card">
<div class="card-header bg-dark">
<h5 class="mb-0">Neue Email</h5>
</div>
<div class="card-body">
<form method="POST" action="/emails">
<input type="hidden" name="action" value="send">
<div class="mb-3">
<label for="to_address" class="form-label">An</label>
<input type="email" class="form-control" id="to_address" name="to_address" required>
</div>
<div class="mb-3">
<label for="subject" class="form-label">Betreff</label>
<input type="text" class="form-control" id="subject" name="subject" required>
</div>
<div class="mb-3">
<label for="body" class="form-label">Nachricht</label>
<textarea class="form-control" id="body" name="body" rows="8" required></textarea>
</div>
<button type="submit" class="btn btn-primary w-100">Versenden</button>
</form>
</div>
</div>
</div>
<!-- Posteingang -->
<div class="col-lg-8">
<div class="card">
<div class="card-header bg-dark d-flex justify-content-between align-items-center">
<h5 class="mb-0">Posteingang</h5>
<span class="badge bg-secondary">{{ emails|length }} Emails</span>
</div>
<div class="card-body p-0" style="max-height:600px;overflow-y:auto;">
{% if email_config_valid and emails %}
{% if emails[0].error is defined and emails[0].error %}
<div class="alert alert-danger m-3">{{ emails[0].error }}</div>
{% else %}
<ul class="list-group list-group-flush">
{% for mail in emails %}
<li class="list-group-item list-group-item-action"
style="cursor:pointer;" onclick="viewEmail('{{ mail.id }}', '{{ mail.subject|e }}', '{{ mail.from|e }}')">
<div class="d-flex justify-content-between align-items-start">
<strong style="font-size:.875rem;">{{ mail.subject }}</strong>
<small style="color:var(--text-muted);white-space:nowrap;margin-left:.5rem;">{{ mail.date[:10] }}</small>
</div>
<div style="font-size:.8rem;color:var(--text-muted);">{{ mail.from }}</div>
<div style="font-size:.78rem;color:var(--text-muted);margin-top:.2rem;">{{ mail.preview }}</div>
</li>
{% endfor %}
</ul>
{% endif %}
{% elif not email_config_valid %}
<div class="text-center py-5" style="color:var(--text-muted);">
<p>Email-Konfiguration erforderlich</p>
</div>
{% else %}
<div class="text-center py-5" style="color:var(--text-muted);">
<p>Keine Emails vorhanden</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Email Modal -->
<div class="modal fade" id="emailModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="emailSubject"></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p style="color:var(--text-muted);font-size:.85rem;"><strong>Von:</strong> <span id="emailFrom"></span></p>
<hr>
<pre id="emailBody" style="background:var(--bg-elevated);color:var(--text-primary);
border-radius:var(--radius-sm);padding:1rem;white-space:pre-wrap;
max-height:400px;overflow-y:auto;font-size:.85rem;">Wird geladen…</pre>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function viewEmail(emailId, subject, from) {
document.getElementById('emailSubject').textContent = subject;
document.getElementById('emailFrom').textContent = from;
document.getElementById('emailBody').textContent = 'Wird geladen…';
const modal = new bootstrap.Modal(document.getElementById('emailModal'));
modal.show();
fetch('/emails/' + emailId)
.then(r => r.json())
.then(d => { document.getElementById('emailBody').textContent = d.content; })
.catch(e => { document.getElementById('emailBody').textContent = 'Fehler: ' + e.message; });
}
</script>
{% endblock %}

262
templates/files.html Normal file
View file

@ -0,0 +1,262 @@
{% extends "base.html" %}
{% block title %}Dateien{% endblock %}
{% block content %}
<div class="page-header">
<h1>Dateiverwaltung</h1>
<p>Uploads, Email-Vorlagen und Projektdokumente</p>
</div>
<div class="row g-4">
<!-- Upload sidebar -->
<div class="col-lg-4">
<div class="card">
<div class="card-header bg-info">
<span>📤 Datei hochladen</span>
</div>
<div class="card-body">
<form method="POST" action="/files" enctype="multipart/form-data">
<div class="mb-3">
<label for="file" class="form-label">Datei auswählen</label>
<input type="file" class="form-control" id="file" name="file" required>
</div>
<p style="color:var(--text-muted);font-size:.76rem;margin-bottom:.75rem;">Max. 16 MB</p>
<button type="submit" class="btn btn-info w-100">Hochladen</button>
</form>
</div>
</div>
<!-- Quick stats -->
<div class="card mt-3">
<div class="card-body p-0">
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center" style="padding:.7rem 1rem;">
<span style="font-size:.85rem;color:var(--text-secondary);">📁 Uploads</span>
<span class="badge bg-secondary">{{ files|length }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center" style="padding:.7rem 1rem;">
<span style="font-size:.85rem;color:var(--text-secondary);">✉ Email-Vorlagen</span>
<span class="badge bg-secondary">{{ email_files|length }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center" style="padding:.7rem 1rem;">
<span style="font-size:.85rem;color:var(--text-secondary);">📄 Projektdokumente</span>
<span class="badge bg-secondary">{{ project_files|length }}</span>
</li>
</ul>
</div>
</div>
</div>
<!-- File lists -->
<div class="col-lg-8">
<!-- Uploads -->
<div class="card mb-3">
<div class="card-header bg-primary d-flex justify-content-between align-items-center">
<span>📁 Hochgeladene Dateien</span>
<span class="badge bg-secondary">{{ files|length }}</span>
</div>
<div class="card-body">
{% if files %}
{% for file in files %}
<div class="file-item">
<span class="file-icon">{{ '📄' if file.name.endswith('.txt') else '📝' if file.name.endswith('.md') else '📋' if file.name.endswith('.docx') else '📁' }}</span>
<div style="flex:1;min-width:0;">
<div class="file-name">{{ file.name }}</div>
<div class="file-meta">{{ (file.size / 1024)|round(1) }} KB · {{ file.modified }}</div>
</div>
<div class="file-actions">
<a href="/files/download/{{ file.name }}" class="btn btn-sm btn-secondary" title="Herunterladen"></a>
<a href="/files/delete/{{ file.name }}" class="btn btn-sm btn-danger"
onclick="return confirm('Datei löschen?')" title="Löschen">✕</a>
</div>
</div>
{% endfor %}
{% else %}
<div class="text-center py-4" style="color:var(--text-muted);">
<div style="font-size:2rem;margin-bottom:.5rem;">📂</div>
<p style="margin:0;font-size:.875rem;">Noch keine Dateien hochgeladen.</p>
</div>
{% endif %}
</div>
</div>
<!-- Email-Vorlagen -->
<div class="card mb-3">
<div class="card-header bg-dark d-flex justify-content-between align-items-center">
<span>✉ Email-Vorlagen <small style="font-weight:400;font-size:.72rem;color:var(--text-muted);margin-left:.4rem;">emails/</small></span>
<span class="badge bg-secondary">{{ email_files|length }}</span>
</div>
<div class="card-body">
{% if email_files %}
{% for file in email_files %}
<div class="file-item" id="file-row-{{ loop.index }}">
<span class="file-icon"></span>
<div style="flex:1;min-width:0;">
<div class="file-name">{{ file.name }}</div>
<div class="file-meta">{{ (file.size / 1024)|round(1) }} KB · {{ file.modified }}</div>
</div>
<div class="file-actions">
<button class="btn btn-sm btn-secondary"
onclick="viewEmailFile(event, '{{ file.name }}')" title="Anzeigen">👁</button>
<button class="btn btn-sm btn-primary"
onclick="editEmailFile('{{ file.name }}')" title="Bearbeiten">✎</button>
<a href="/files/email/delete/{{ file.name }}" class="btn btn-sm btn-danger"
onclick="return confirm('Email-Vorlage löschen?')" title="Löschen">✕</a>
</div>
</div>
{% endfor %}
{% else %}
<div class="text-center py-4" style="color:var(--text-muted);">
<div style="font-size:2rem;margin-bottom:.5rem;"></div>
<p style="margin:0;font-size:.875rem;">Keine Email-Vorlagen gefunden.</p>
</div>
{% endif %}
</div>
</div>
<!-- Projektdokumente -->
<div class="card">
<div class="card-header bg-secondary d-flex justify-content-between align-items-center">
<span>📄 Projektdokumente <small style="font-weight:400;font-size:.72rem;color:var(--text-muted);margin-left:.4rem;">Arbeitsverzeichnis</small></span>
<span class="badge bg-secondary">{{ project_files|length }}</span>
</div>
<div class="card-body">
{% if project_files %}
{% for file in project_files %}
<div class="file-item">
<span class="file-icon">{{ '📋' if file.name.endswith('.docx') else '📝' if file.name.endswith('.md') else '📄' }}</span>
<div style="flex:1;min-width:0;">
<div class="file-name">{{ file.name }}</div>
<div class="file-meta">{{ (file.size / 1024)|round(1) }} KB · {{ file.modified }}</div>
</div>
<div class="file-actions">
<button class="btn btn-sm btn-secondary"
onclick="viewProjectFile(event, '{{ file.name }}')" title="Anzeigen">👁</button>
</div>
</div>
{% endfor %}
{% else %}
<div class="text-center py-4" style="color:var(--text-muted);">
<p style="margin:0;font-size:.875rem;color:var(--text-muted);">Keine Projektdokumente gefunden.</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- File Viewer Modal -->
<div class="modal fade" id="fileModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="fileModalTitle">Datei</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<pre id="fileModalContent" style="background:var(--bg-base);color:#a5f3fc;
border-radius:var(--radius-sm);padding:1.1rem;white-space:pre-wrap;
word-break:break-word;max-height:70vh;overflow-y:auto;font-size:.83rem;
border:1px solid var(--border);font-family:'JetBrains Mono',monospace;">Wird geladen…</pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
</div>
</div>
</div>
</div>
<!-- Email Editor Modal -->
<div class="modal fade" id="editModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">✎ Bearbeiten: <span id="editModalFilename"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<textarea id="editModalContent" class="inline-editor" spellcheck="false"></textarea>
<div id="editSaveStatus" style="font-size:.8rem;color:var(--text-muted);margin-top:.5rem;height:1.2em;"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button type="button" class="btn btn-primary" onclick="saveEmailFile()">💾 Speichern</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let currentEditFile = null;
function openFileModal(title, url) {
document.getElementById('fileModalTitle').textContent = title;
const content = document.getElementById('fileModalContent');
content.textContent = 'Wird geladen…';
const modal = new bootstrap.Modal(document.getElementById('fileModal'));
modal.show();
fetch(url)
.then(r => r.json())
.then(d => { content.textContent = d.content || d.error || '(leer)'; })
.catch(e => { content.textContent = 'Fehler: ' + e.message; });
}
function viewEmailFile(event, name) {
event.preventDefault();
openFileModal(name, '/files/email/view/' + encodeURIComponent(name) + '?json=1');
}
function viewProjectFile(event, name) {
event.preventDefault();
openFileModal(name, '/files/project/view/' + encodeURIComponent(name) + '?json=1');
}
function editEmailFile(name) {
currentEditFile = name;
document.getElementById('editModalFilename').textContent = name;
const textarea = document.getElementById('editModalContent');
const status = document.getElementById('editSaveStatus');
textarea.value = 'Wird geladen…';
status.textContent = '';
const modal = new bootstrap.Modal(document.getElementById('editModal'));
modal.show();
fetch('/files/email/view/' + encodeURIComponent(name) + '?json=1')
.then(r => r.json())
.then(d => { textarea.value = d.content || d.error || ''; })
.catch(e => { textarea.value = 'Fehler: ' + e.message; });
}
function saveEmailFile() {
if (!currentEditFile) return;
const content = document.getElementById('editModalContent').value;
const status = document.getElementById('editSaveStatus');
status.style.color = 'var(--text-muted)';
status.textContent = 'Speichern…';
fetch('/files/email/save/' + encodeURIComponent(currentEditFile), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content })
})
.then(r => r.json())
.then(d => {
if (d.ok) {
status.style.color = 'var(--success)';
status.textContent = '✓ Gespeichert';
setTimeout(() => { status.textContent = ''; }, 3000);
} else {
status.style.color = 'var(--danger)';
status.textContent = '✗ Fehler: ' + (d.error || 'Unbekannt');
}
})
.catch(e => {
status.style.color = 'var(--danger)';
status.textContent = '✗ ' + e.message;
});
}
</script>
{% endblock %}

126
templates/index.html Normal file
View file

@ -0,0 +1,126 @@
{% extends "base.html" %}
{% block title %}Dashboard{% endblock %}
{% block content %}
<div class="hero-section">
<div class="d-flex align-items-center justify-content-between flex-wrap gap-3">
<div>
<h1 style="font-size:1.75rem;margin-bottom:.4rem;-webkit-text-fill-color:unset;background:none;color:var(--text-primary);">
Diversity-Ball Wien 2026
</h1>
<p style="color:var(--text-secondary);margin:0;font-size:.9rem;">
Agenten-Orchestrierung · Samstag 5. September · Wiener Rathaus
</p>
</div>
<div class="d-flex gap-2 flex-wrap">
<a href="/orchestrator" class="btn btn-primary">🤖 Orchestrator</a>
<a href="/chat" class="btn btn-secondary">💬 Chat</a>
<a href="/files" class="btn btn-secondary">📂 Dateien</a>
</div>
</div>
</div>
<!-- Stats -->
<div class="row g-3 mb-4">
<div class="col-6 col-md-3">
<div class="stat-card">
<div class="stat-number">{{ agents|length }}</div>
<div class="stat-label">Agenten</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="stat-card">
<div class="stat-number">{{ recent_tasks|length }}</div>
<div class="stat-label">Letzte Tasks</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="stat-card">
<div class="stat-number">5. Sep</div>
<div class="stat-label">Event-Datum</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="stat-card">
<div class="stat-number">3.500</div>
<div class="stat-label">Gäste</div>
</div>
</div>
</div>
<!-- Agents -->
<div class="d-flex align-items-center justify-content-between mb-3">
<h3 style="margin:0;">Verfügbare Agenten</h3>
<a href="/agents" class="btn btn-outline-secondary btn-sm">Alle verwalten</a>
</div>
<div class="row g-3 mb-4">
{% for key, agent in agents.items() %}
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="card agent-card h-100">
<div class="card-body">
<div class="d-flex align-items-center gap-2 mb-2">
<span style="width:8px;height:8px;border-radius:50%;background:var(--success);flex-shrink:0;box-shadow:0 0 6px var(--success);"></span>
<h5 style="margin:0;font-size:.875rem;font-weight:600;">{{ agent.name }}</h5>
</div>
<p style="font-size:.775rem;color:var(--text-muted);line-height:1.45;margin-bottom:.75rem;">
{{ agent.description[:90] }}{% if agent.description|length > 90 %}…{% endif %}
</p>
<span class="badge bg-success">aktiv</span>
</div>
<div class="card-footer">
<a href="/agents?edit={{ key }}" class="btn btn-outline-primary btn-sm w-100">⚙ Bearbeiten</a>
</div>
</div>
</div>
{% endfor %}
</div>
<!-- Recent Tasks -->
{% if recent_tasks %}
<div class="d-flex align-items-center justify-content-between mb-3">
<h3 style="margin:0;">Letzte Tasks</h3>
<a href="/tasks" class="btn btn-outline-secondary btn-sm">Alle Tasks</a>
</div>
<div class="card">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>#</th>
<th>Titel</th>
<th>Agent</th>
<th>Status</th>
<th>Erstellt</th>
</tr>
</thead>
<tbody>
{% for task in recent_tasks %}
<tr>
<td><span style="color:var(--text-muted);font-size:.8rem;">#{{ task.id }}</span></td>
<td style="font-weight:500;">{{ task.title }}</td>
<td style="font-size:.8rem;color:var(--text-muted);">{{ task.assigned_agent }}</td>
<td>
{% if task.status == 'pending' %}
<span class="badge bg-warning">Pending</span>
{% elif task.status == 'in_progress' %}
<span class="badge bg-primary">Läuft</span>
{% elif task.status == 'completed' %}
<span class="badge bg-success">Fertig</span>
{% elif task.status == 'error' %}
<span class="badge bg-danger">Fehler</span>
{% else %}
<span class="badge bg-secondary">{{ task.status }}</span>
{% endif %}
</td>
<td style="color:var(--text-muted);font-size:.78rem;">{{ task.created }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
{% endblock %}

157
templates/orchestrator.html Normal file
View file

@ -0,0 +1,157 @@
{% extends "base.html" %}
{% block title %}Orchestrator{% endblock %}
{% block content %}
<div class="page-header">
<h1>Master-Orchestrator</h1>
<p>Delegiert automatisch an den passenden Agenten</p>
</div>
{% if knowledge_loaded %}
<div class="alert alert-success mb-4">
<strong>Wissensdatenbank geladen</strong> · Agenten-Prompts initialisiert
</div>
{% endif %}
<div class="row g-4">
<!-- Sidebar -->
<div class="col-lg-4">
<div class="card mb-3">
<div class="card-header bg-dark">
<h5 class="mb-0">Prompt eingeben</h5>
</div>
<div class="card-body">
<form id="promptForm" method="POST" action="/orchestrator">
<div class="mb-3">
<label for="prompt" class="form-label">Ihre Anfrage</label>
<textarea class="form-control" id="prompt" name="prompt" rows="5"
placeholder="Was soll erledigt werden?" required></textarea>
</div>
<button type="button" class="btn btn-primary w-100 mb-2" id="streamBtn"
onclick="sendPromptWithStream()">Live-Antwort anfordern</button>
<button type="submit" class="btn btn-secondary w-100">Klassisch senden</button>
</form>
</div>
</div>
<div class="card">
<div class="card-header bg-secondary">
<h6 class="mb-0">Aktive Agenten</h6>
</div>
<div class="card-body p-0">
<ul class="list-group list-group-flush">
{% for key, agent in agents.items() %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<span style="font-size:.85rem;">{{ agent.name }}</span>
<span class="badge bg-success" style="font-size:.65rem;">aktiv</span>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
<!-- Chat -->
<div class="col-lg-8">
<div class="card">
<div class="card-header bg-dark d-flex justify-content-between align-items-center">
<h5 class="mb-0">Orchestrator-Chat</h5>
<small style="color:var(--text-muted);">Automatische Agenten-Delegation</small>
</div>
<div class="card-body chat-container" id="chatContainer">
{% if chat_history %}
{% for chat in chat_history %}
<div class="chat-message">
<div class="chat-timestamp">
{{ chat.timestamp }} ·
<span class="badge bg-primary" style="font-size:.65rem;">{{ chat.agent }}</span>
</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>
{% endfor %}
{% else %}
<div class="text-center py-5" style="color:var(--text-muted);" id="emptyState">
<p style="font-size:2rem;margin-bottom:.5rem;">🤖</p>
<p>Noch keine Anfragen.<br>Der Orchestrator delegiert automatisch an den passenden Agenten.</p>
<hr>
<p style="font-size:.8rem;">
✓ Immer zuerst delegieren · ✓ Neuen Agenten erstellen falls nötig · ✓ Niemals selbst ausführen
</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function sendPromptWithStream() {
const prompt = document.getElementById('prompt').value.trim();
const streamBtn = document.getElementById('streamBtn');
if (!prompt) { alert('Bitte Anfrage eingeben!'); return; }
streamBtn.disabled = true;
streamBtn.textContent = '⏳ Agent arbeitet…';
const chatContainer = document.getElementById('chatContainer');
const emptyState = document.getElementById('emptyState');
if (emptyState) emptyState.remove();
const msgDiv = document.createElement('div');
msgDiv.className = 'chat-message';
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-prompt mt-1"><strong>Sie:</strong> ${prompt}</div>
<div class="chat-response mt-1" id="responseDiv"><strong>Orchestrator:</strong> <span id="responseText">⏳ Agent arbeitet…</span></div>
`;
chatContainer.insertBefore(msgDiv, chatContainer.firstChild);
fetch('/api/agent-stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt })
}).then(response => {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
function read() {
reader.read().then(({ done, value }) => {
if (done) {
streamBtn.disabled = false;
streamBtn.textContent = 'Live-Antwort anfordern';
return;
}
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) {
if (!line.startsWith('data: ')) continue;
try {
const event = JSON.parse(line.slice(6));
if (event.type === 'agent_selected') {
document.getElementById('agentBadge').textContent = event.agent;
} else if (event.type === 'response_chunk') {
const t = document.getElementById('responseText');
if (t.textContent.startsWith('⏳')) t.textContent = '';
t.textContent += event.text;
} else if (event.type === 'error') {
document.getElementById('responseText').textContent = '❌ ' + event.message;
}
} catch(e) {}
}
read();
});
}
read();
}).catch(err => {
document.getElementById('responseText').textContent = '❌ ' + err.message;
streamBtn.disabled = false;
streamBtn.textContent = 'Live-Antwort anfordern';
});
}
</script>
{% endblock %}

119
templates/settings.html Normal file
View file

@ -0,0 +1,119 @@
{% extends "base.html" %}
{% block title %}Einstellungen{% endblock %}
{% block content %}
<div class="page-header mb-4">
<h1 class="page-title">Einstellungen</h1>
<p class="page-subtitle text-muted">Poller-Konfiguration &amp; System-Status</p>
</div>
<div class="row g-4">
<!-- Poller-Einstellungen -->
<div class="col-lg-6">
<div class="card h-100">
<div class="card-header">
<span class="me-2"></span> Email-Poller
</div>
<div class="card-body">
<form method="POST">
<div class="mb-4">
<label class="form-label fw-semibold">Poll-Intervall <span class="text-muted fw-normal">(Sekunden)</span></label>
<input type="number" class="form-control" name="poll_interval"
value="{{ poller_settings.poll_interval }}" min="10" max="3600" required>
<div class="form-text">Wie oft der Poller den IMAP-Eingang prüft. Aktuell: <strong>{{ poller_settings.poll_interval }}s</strong> ({{ (poller_settings.poll_interval / 60) | round(1) }} min)</div>
</div>
<div class="mb-4">
<label class="form-label fw-semibold">Failsafe-Fenster <span class="text-muted fw-normal">(Sekunden)</span></label>
<input type="number" class="form-control" name="failsafe_window"
value="{{ poller_settings.failsafe_window }}" min="30" max="86400" required>
<div class="form-text">
Wie lange ein Task laufen darf bevor er als hängend gilt und erneut verarbeitet wird.
Aktuell: <strong>{{ poller_settings.failsafe_window }}s</strong> ({{ (poller_settings.failsafe_window / 60) | round(1) }} min).
<span class="text-warning">Muss größer als das Poll-Intervall sein.</span>
</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Speichern</button>
<a href="/settings" class="btn btn-outline-secondary">Zurücksetzen</a>
</div>
</form>
</div>
</div>
</div>
<!-- Journal-Status -->
<div class="col-lg-6">
<div class="card h-100">
<div class="card-header">
<span class="me-2">📋</span> Email-Journal Status
</div>
<div class="card-body">
{% if journal_stats %}
<table class="table table-sm">
<thead>
<tr>
<th>Status</th>
<th class="text-end">Anzahl</th>
</tr>
</thead>
<tbody>
{% for status, count in journal_stats.items() %}
<tr>
<td>
{% if status == 'completed' %}
<span class="badge" style="background:var(--success)">✓ {{ status }}</span>
{% elif status == 'queued' %}
<span class="badge" style="background:var(--warning);color:#000">⏳ {{ status }}</span>
{% elif status == 'skipped' %}
<span class="badge" style="background:var(--text-muted)">— {{ status }}</span>
{% elif status == 'error' %}
<span class="badge" style="background:var(--danger)">✗ {{ status }}</span>
{% else %}
<span class="badge bg-secondary">{{ status }}</span>
{% endif %}
</td>
<td class="text-end fw-semibold">{{ count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="text-muted">Noch keine Einträge im Journal.</p>
{% endif %}
<hr class="my-3" style="border-color:var(--border)">
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">Journal-Datenbank: <code>email_journal.db</code></small>
<form method="POST" action="/settings/journal-clear"
onsubmit="return confirm('Alle abgeschlossenen Journal-Einträge löschen?')">
<button type="submit" class="btn btn-sm btn-outline-danger">Abgeschlossene löschen</button>
</form>
</div>
</div>
</div>
</div>
<!-- Whitelist -->
<div class="col-12">
<div class="card">
<div class="card-header">
<span class="me-2">🔒</span> Email-Whitelist
</div>
<div class="card-body">
<p class="text-muted mb-2">Nur Emails von diesen Absendern werden verarbeitet (aktuell hardcoded in <code>app.py</code>):</p>
<div class="d-flex flex-wrap gap-2">
{% set whitelist = ['eric.fischer@signtime.media', 'p.dyderski@live.at', 'georg.tschare@gmail.com', 'georg.tschare@signtime.media'] %}
{% for addr in whitelist %}
<code class="px-2 py-1 rounded" style="background:var(--bg-elevated);color:var(--accent-light)">{{ addr }}</code>
{% endfor %}
<code class="px-2 py-1 rounded" style="background:var(--bg-elevated);color:var(--accent2)">*@diversityball.at</code>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

118
templates/tasks.html Normal file
View file

@ -0,0 +1,118 @@
{% extends "base.html" %}
{% block title %}Tasks{% endblock %}
{% block content %}
<div class="page-header">
<h1>Task-Verwaltung</h1>
<p>Manuelle und automatische Email-Tasks</p>
</div>
<div class="row g-4">
<div class="col-lg-4">
<div class="card">
<div class="card-header bg-success">
<h5 class="mb-0">Neuen Task erstellen</h5>
</div>
<div class="card-body">
<form method="POST" action="/tasks">
<div class="mb-3">
<label for="title" class="form-label">Titel</label>
<input type="text" class="form-control" id="title" name="title" placeholder="Task-Titel" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Beschreibung</label>
<textarea class="form-control" id="description" name="description" rows="3"
placeholder="Optionale Beschreibung"></textarea>
</div>
<div class="mb-3">
<label for="assigned_agent" class="form-label">Agent (optional)</label>
<select class="form-select" id="assigned_agent" name="assigned_agent">
<option value="">— Agent wählen —</option>
{% for key, agent in agents.items() %}
<option value="{{ key }}">{{ agent.name }}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-success w-100">Task erstellen</button>
</form>
</div>
</div>
</div>
<div class="col-lg-8">
<div class="card">
<div class="card-header bg-primary d-flex justify-content-between align-items-center">
<h5 class="mb-0">Alle Tasks</h5>
<span class="badge bg-secondary">{{ tasks|length }}</span>
</div>
<div class="card-body p-0">
{% if tasks %}
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>#</th>
<th>Titel</th>
<th>Agent</th>
<th>Status</th>
<th>Erstellt</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
{% for task in tasks %}
<tr>
<td style="color:var(--text-muted);">{{ task.id }}</td>
<td>
<strong>{{ task.title }}</strong>
{% if task.type == 'email' %}
<span class="badge bg-info ms-1" title="Von: {{ task.reply_to }}">Email</span>
{% endif %}
{% if task.description %}
<div style="font-size:.75rem;color:var(--text-muted);">
{{ task.description[:60] }}{% if task.description|length > 60 %}…{% endif %}
</div>
{% endif %}
</td>
<td style="font-size:.8rem;">{{ task.assigned_agent }}</td>
<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>
<td style="color:var(--text-muted);font-size:.75rem;">{{ task.created }}</td>
<td>
{% if task.type == 'email' %}
<span style="color:var(--text-muted);font-size:.75rem;">Auto</span>
{% elif task.status == 'pending' %}
<a href="/tasks/update/{{ task.id }}/in_progress" class="btn btn-sm btn-primary">Start</a>
{% elif task.status == 'in_progress' %}
<a href="/tasks/update/{{ task.id }}/completed" class="btn btn-sm btn-success">Fertig</a>
{% else %}
<span style="color:var(--text-muted);"></span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-5" style="color:var(--text-muted);">
<p style="font-size:2rem;">📋</p>
<p>Noch keine Tasks. Erstellen Sie den ersten Task!</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

146
test_features.py Normal file
View file

@ -0,0 +1,146 @@
#!/usr/bin/env python3
"""
Test Script für neue Features
- Streaming UI (SSE)
- Email Integration (IMAP/SMTP)
"""
import json
import sys
def test_imports():
"""Teste dass alle notwendigen Importe funktionieren"""
print("✓ Testing Imports...")
try:
import imaplib
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from flask import Flask, Response
print(" ✓ All imports successful")
return True
except ImportError as e:
print(f" ✗ Import Error: {e}")
return False
def test_app_syntax():
"""Teste dass app.py syntaktisch korrekt ist"""
print("\n✓ Testing app.py syntax...")
try:
import app
print(" ✓ app.py loads successfully")
# Check if new routes exist
routes = [str(rule) for rule in app.app.url_map.iter_rules()]
if '/api/agent-stream' in routes:
print(" ✓ /api/agent-stream route exists")
else:
print(" ✗ /api/agent-stream route NOT found")
if '/emails' in routes:
print(" ✓ /emails route exists")
else:
print(" ✗ /emails route NOT found")
return True
except Exception as e:
print(f" ✗ Error: {e}")
return False
def test_email_config():
"""Teste Email-Konfiguration"""
print("\n✓ Testing Email Configuration...")
import os
config = {
'IMAP_SERVER': os.getenv('IMAP_SERVER', 'imap.gmail.com'),
'SMTP_SERVER': os.getenv('SMTP_SERVER', 'smtp.gmail.com'),
'EMAIL_ADDRESS': os.getenv('EMAIL_ADDRESS', ''),
'EMAIL_PASSWORD': os.getenv('EMAIL_PASSWORD', ''),
}
configured = bool(config['EMAIL_ADDRESS'] and config['EMAIL_PASSWORD'])
status = "✓ Configured" if configured else "⚠ Not Configured"
print(f" {status}")
print(f" - IMAP: {config['IMAP_SERVER']}")
print(f" - SMTP: {config['SMTP_SERVER']}")
print(f" - Email: {config['EMAIL_ADDRESS'][:20] if config['EMAIL_ADDRESS'] else 'Not set'}...")
if not configured:
print(" Note: Set environment variables to enable email features")
return True
def test_templates():
"""Teste dass neue Templates existieren"""
print("\n✓ Testing Templates...")
import os
templates = {
'emails.html': os.path.exists('templates/emails.html'),
'orchestrator.html': os.path.exists('templates/orchestrator.html'),
}
for template, exists in templates.items():
status = "" if exists else ""
print(f" {status} {template}")
return all(templates.values())
def test_sse_response():
"""Teste SSE Response Header"""
print("\n✓ Testing SSE Support...")
try:
from flask import Flask, Response
app = Flask(__name__)
@app.route('/test-sse')
def test_sse():
def generate():
yield f"data: {json.dumps({'type': 'test'})}\n\n"
return Response(generate(), mimetype='text/event-stream')
print(" ✓ SSE Response configured")
return True
except Exception as e:
print(f" ✗ Error: {e}")
return False
def main():
print("=" * 60)
print("🧪 Testing New Features")
print("=" * 60)
results = [
("Imports", test_imports()),
("App Syntax", test_app_syntax()),
("Email Config", test_email_config()),
("Templates", test_templates()),
("SSE Support", test_sse_response()),
]
print("\n" + "=" * 60)
print("📊 Test Summary")
print("=" * 60)
for name, result in results:
status = "✓ PASS" if result else "✗ FAIL"
print(f"{status}: {name}")
passed = sum(1 for _, r in results if r)
total = len(results)
print(f"\nTotal: {passed}/{total} passed")
if passed == total:
print("\n✨ All tests passed!")
return 0
else:
print("\n⚠️ Some tests failed")
return 1
if __name__ == '__main__':
sys.exit(main())