feat: Dynamische KI-Modelle, verbessertes Memory-System und Chat-Überarbeitung

🎯 KI-Modellverwaltung
- Dynamisches Laden verfügbarer Modelle via opencode models
- 29 Modelle verfügbar (opencode, anthropic, ollama)
- Gruppierung nach Anbieter in UI
- Cache-Mechanismus (1h TTL) für Performance
- API-Endpoint /api/models für Modellabfrage

🧠 Memory-System komplett überarbeitet
- JSON-basierte strukturierte Erinnerungen statt Markdown-Chaos
- Separate Memory-Typen: tasks.json, notes.json, research.json
- Automatische Memory-Zusammenfassung im Systemprompt
- Limitierung auf letzte 100 Einträge pro Typ
- Vollständige Task-Ergebnisse statt abgeschnittener Texte

📁 Agenten-Ordnerstruktur
- work/ Verzeichnis für Agent-Dateien
- memory/ Verzeichnis für strukturierte Erinnerungen
- Agenten arbeiten nur in eigenem work-Verzeichnis
- Absolute Pfade werden übergeben
- Dateien-UI zeigt Agent-Work-Folders

💬 Chat-System überarbeitet
- Echte Agent-Ausführung statt Mock-Responses
- Server-Sent Events für Live-Streaming
- Session-basierte Chat-History
- Loading-Spinner und Status-Anzeigen
- Automatisches Speichern in Session

🎭 Personality Integration
- personality.md wird jetzt geladen
- Persönlichkeit vor Systemprompt eingefügt
- Gilt für alle: Chat, Tasks, Orchestrator, Email-Poller

 Weitere Verbesserungen
- Alle Agenten nutzen execute_agent_task() zentral
- Memory-Speicherung nach jedem Task
- Work-Files in Datei-Verwaltung sichtbar
- System-Dateien ausgeblendet
- API-Route für Agent-Work-Dateien
This commit is contained in:
pdyde 2026-02-21 11:44:06 +01:00
parent 84b2fe3dd7
commit 93eb8c6d47
83 changed files with 1692 additions and 1517 deletions

View file

@ -0,0 +1,182 @@
# Diversity-Ball Wien | Durchgetakteter Event-Plan
## Samstag, 5. September 2026 | 18:00 02:00 Uhr | Wiener Rathaus, Festsaal
---
## 📋 MASTER-ZEITPLAN
| Zeit | Programmpunkt | Catering | Musik | Technik/Stage | Verantwortlich |
|------|---------------|----------|-------|---------------|----------------|
| **17:00** | **T-1 Stunde: Final-Check** | Küche: Warmlaufen | Soundcheck final | Bühne ready | Location-Manager |
| **17:30** | Team-Einweisung | Service: Positionsbezug | DJ/Live: Setup | Licht: Test | Catering-Manager |
| **18:00** | **EINLASS** | Sektempfang startet | Hintergrundmusik (Jazz/Lounge) | Ambient-Licht | Host-Team |
| **18:00-19:00** | Sektempfang & Welcome-Drinks | 2 Getränke p.P. inkludiert | Soft-Musik | Spotlight Bühne aus | Bar-Team |
| **18:45** | Gäste-Placement Tische | Nachservice Getränke | Übergang zu Programm-Musik | - | Service-Manager |
| **19:00** | **ERÖFFNUNG** | - | Intro-Musik (feierlich) | Vollbeleuchtung Bühne | Moderator:in |
| **19:00-19:15** | Eröffnungsrede (Diversity-Botschaft) | - | Stille | Spot auf Redner:in | Program-Manager |
| **19:15-19:25** | Begrüßung Ehrengäste | - | Musikalische Untermalung | - | Moderator:in |
| **19:25-19:35** | Organisatorisches / Hausregeln | - | leise Musik | Bildschirme: Infos | - |
| **19:35-19:45** | Pause / Atmosphäre | Getränke nachschenken | Überleitung | Licht dimmen | Bar |
| **19:45-20:00** | **GANG 1: Appetizer** | Tablettservice (2/person) | Hintergrundmusik | Gedämpftes Licht | Catering-Manager |
| **20:00-20:30** | **GANG 2: Hauptgang** | Plated Service (3 Varianten) | Sanfte Hintergrundmusik | Tischlicht-Setup | Service-Captains |
| **20:30-21:00** | Hauptgang + Gespräch | Nachservice | Music Transition | Licht anpassen | - |
| **21:00-21:15** | Pause / Sektempfang | Sorbet optional | leise Musik | Ambient | - |
| **21:15-21:30** | **GANG 3: Dessert** | Buffet + Stationen | süffige Musik | Warmlicht | Catering |
| **21:30** | **TANZFLÄCHE FREI** | Kaffee / Digestif | DJ/Live-Band startet | Tanzlicht an | Musik-Rechte |
| **21:30-22:15** | **TANZ & UNTERHALTUNG** | Late-Night-Station öffnet | DJ / Live-Act | Tanzflächen-Beleuchtung | Program-Manager |
| **22:15-22:45** | **DIVERSITY AWARDS** | - | Moderationsmusik | Spotlight Bühne | Program-Manager |
| **22:45-23:15** | Networking / Tanz | Getränke-Bar | Open Floor | Tanzlicht | Bar-Team |
| **23:15-23:30** | Pause | - | leise Überleitung | Licht dimmen | - |
| **23:30-00:15** | **TOMBOLA / VERSTEIGERUNG** | Snacks | Auktionsmusik | Bühne + Screen | Fundraising |
| **00:15-00:45** | Music Act (Live-Performance) | - | Live-Musik | Vollbühne | Musik-Rechte |
| **00:45-01:15** | Weiterfeiern / Tanz | Late-Night (2. Runde) | DJ-Set | Tanzlicht | - |
| **01:15-01:30** | **ABSCHLUSSREDEN** | - | leise Musik | Spot | Moderator:in |
| **01:30-01:45** | Dankeschön / Closing | - | Finale-Musik | Licht-Countdown | - |
| **01:45-02:00** | **ENDE / AUSSCHANK** | Letzte Getränke | Music aus | Abbau Start | - |
| **02:00** | **EVENT ENDE** | - | - | - | - |
---
## 🍽️ CATERING DETAIL
### Menü-Ablauf
| Gang | Zeit | Gericht | Menge | Special |
|------|------|---------|-------|---------|
| **Appetizer** | 19:45-20:00 | 3 Optionen (Fisch/Veggie/Vegan) | 7.700 Stück | Tablettservice |
| **Hauptgang** | 20:00-20:30 | Wild / Zander / Pilz-Ragout | 3.675 P. | Plated |
| **Dessert** | 21:15-21:30 | Mousse / Strudel / Panna Cotta | 4.830 St. | Buffet |
| **Late-Night** | 21:30-01:30 | Mini-Burger / Würstel / Spieße | 6.000 St. | Stationen |
### Ernährungsstationen
- 🥬 **Vegan-Station**: Komplett pflanzlich
- 🌾 **Glutenfrei-Station**: Separate Ausgabe
- 🥛 **Laktosefrei-Station**: Milchfreie Alternativen
### Service-Personal: 168 Personen
| Zeit | Focus |
|------|-------|
| 18:00-19:00 | Sektempfang (max. Output) |
| 19:45-20:30 | Hauptgang (Peak) |
| 21:15-01:30 | Late-Night / Bar |
---
## 🎵 MUSIK & ENTERTAINMENT
### Musik-Acts
| Zeit | Programmpunkt | Musikart | AKM-relevant |
|------|---------------|----------|--------------|
| 18:00-19:00 | Sektempfang | Jazz/Lounge | ✅ |
| 19:00-21:30 | Menü | Ambient/Classical | ✅ |
| 21:30-00:45 | Tanzfläche | DJ + Live-Act | ✅ |
| 00:45-01:30 | Feier | DJ-Set | ✅ |
### AKM-Kosten (geschätzt)
- Pauschale (3.500 Pers., 8h, Tanz): ~€ 5.500
- Mit IG Kultur 40%: **~€ 3.300**
- + AUME/LSG-Zuschläge: ~€ 480
- **Gesamt: ~€ 3.780**
### Technik-Bedarf
| Element | Anbieter | Budget |
|---------|----------|--------|
| Licht | NUNTIO/VA.TEC | ~€ 15.000 |
| Ton | extern | ~€ 10.000 |
| Bühne | mobiler Aufbau | ~€ 5.000 |
| **Gesamt** | | **~€ 30.000** |
---
## 🎯 STAGES & BEREICHE
### Räumliche Aufteilung
```
┌─────────────────────────────────────────────────────────────┐
│ FESTSAAL (1.420 m²) │
├─────────────────────────────────────────────────────────────┤
│ [Bühne Nische] │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Tanzfläche (300 m²) │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │Tisch│ │Tisch│ │Tisch│ │Tisch│ │Tisch│ │Tisch│ ← 3.500 │
│ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘ │
│ │
│ [Büfett] [Vegan] [GF] [Late-Night] [Eingang] │
│ │
│ [Bühne Sichte] │
└─────────────────────────────────────────────────────────────┘
```
### Stationen
1. **Haupt-Buffet**: Dessert + Kaffee
2. **Vegan-Station**: Vollständig pflanzlich
3. **Allergen-Station**: Gluten-/Laktosefrei
4. **Late-Night-Station**: Snacks bis 01:30
5. **Haupt-Bar**: Getränkeausgabe
6. **VIP-Bereich** (optional): Sep. Service
---
## 👥 PERSONAL-ÜBERSICHT
| Bereich | Personen | Start | Ende |
|---------|----------|-------|------|
| Catering-Service | 120 | 16:00 | 03:00 |
| Küche | 25 | 08:00 | 02:00 |
| Bar | 12 | 17:00 | 03:00 |
| Host/Hostessen | 10 | 17:00 | 02:00 |
| Security | 15 | 17:00 | 03:00 |
| Technik | 8 | 14:00 | 03:00 |
| Moderation | 2 | 18:00 | 02:00 |
| **GESAMT** | **192** | | |
---
## 💰 BUDGET-ÜBERSICHT
| Posten | Budget | Status |
|--------|--------|--------|
| Location (Rathaus) | € 25.000 | Anfrage läuft |
| Catering (Speisen) | € 280.000 | Fix: Rathauskeller |
| Getränke | € 0 | Gesponsert |
| Technik | € 30.000 | Anfrage |
| AKM/GEMA | € 4.000 | Kalkuliert |
| Personal | € 30.000 | - |
| Barrierefreiheit | € 20.000 | - |
| Deko | € 15.000 | - |
| Security | € 10.000 | Anfrage |
| Versicherung | € 5.000 | - |
| Reserve | € 50.000 | - |
| **GESAMT** | **~€ 469.000** | Budget: € 750.000 |
---
## ⚠️ KRITISCHE PUNKTE
| Thema | Verantwortlich | Deadline | Status |
|-------|----------------|-----------|--------|
| Rathaus-Verfügbarkeit | Location-Manager | Sofort | 🔴 Offen |
| Getränke-Sponsor | Fundraising | 30.04.2026 | 🔴 Offen |
| MA 36 Genehmigung | Orchestrator | 4 Wochen vor | 🟡 In Bearbeitung |
| AKM-Anmeldung | Musik-Rechte | 28.08.2026 | 🟢 Okay |
| Gemeinnützigkeit | Tax-Advisor | 01.07.2026 | 🟡 In Bearbeitung |
| ÖGS-Dolmetscher | Location-Manager | 15.08.2026 | 🔴 Offen |
---
## 📞 KONTAKTE
| Rolle | Name | Telefon | Email |
|-------|------|---------|-------|
| Catering-Manager | Rathauskeller | +43 1 405 00 | office@wiener-rathauskeller.at |
| Location | Rathausverwaltung | +43 1 4000 | rathaus@wien.gv.at |
| Technik | NUNTIO | +43 1 68 98 177 | info@nuntio.at |
| AKM | Veranstaltung | +43 1 505 62 62 | veranstaltung@akm.at |
| MA 36 | Genehmigung | +43 1 4000-8360 | veranstaltung@ma36.wien.at |
---
*Stand: 20.2.2026 | Erstellt für Diversity-Ball Wien*

View file

@ -1,131 +0,0 @@
═══════════════════════════════════════════════════════════════════════════════
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
═══════════════════════════════════════════════════════════════════════════════

Binary file not shown.

Binary file not shown.

View file

@ -1,86 +0,0 @@
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

View file

@ -1,32 +1,38 @@
{
"researcher": {
"model": "opencode/big-pickle"
"model": "anthropic/claude-haiku-4-5"
},
"location_manager": {
"model": "opencode/big-pickle"
"model": "anthropic/claude-haiku-4-5"
},
"catering_manager": {
"model": "opencode/big-pickle"
"model": "anthropic/claude-haiku-4-5"
},
"program_manager": {
"model": "opencode/big-pickle"
"model": "anthropic/claude-haiku-4-5"
},
"document_editor": {
"model": "opencode/big-pickle"
"model": "anthropic/claude-haiku-4-5"
},
"tax_advisor": {
"model": "opencode/big-pickle"
"model": "anthropic/claude-sonnet-4-6"
},
"musik_rechte_advisor": {
"model": "opencode/big-pickle"
"model": "anthropic/claude-opus-4-0"
},
"zusammenfasser": {
"model": "opencode/big-pickle"
"model": "anthropic/claude-haiku-4-5"
},
"social_media_manager": {
"model": "opencode/big-pickle"
"model": "anthropic/claude-haiku-4-5"
},
"negotiator": {
"model": "opencode/big-pickle"
"model": "anthropic/claude-opus-4-0"
},
"budget_manager": {
"model": "anthropic/claude-haiku-4-5"
},
"orchestrator": {
"model": "anthropic/claude-sonnet-4-6"
}
}

View file

@ -12,14 +12,3 @@
- **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,2 @@
Du bist trocken, steif und hörst auf den Namen Hans-Ruedi

View file

@ -0,0 +1,79 @@
# Budget Manager - Systemprompt
Du bist der **Budget Manager** für den Diversity-Ball Wien. Deine Aufgabe ist die Budgetüberwachung und Kostenkontrolle.
## Grundhaltung
**Stets zum Vorteil des Diversity-Ball (Veranstalter):**
- Optimiere alle Ausgaben zugunsten des Veranstalters
- Frage regelmäßig den Budgetstand ab und kommuniziere ihn proaktiv
- Weise andere Agenten darauf hin, kostengünstig zu arbeiten
- Verhindere Budgetüberschreitungen durch frühzeitige Warnungen
## Budget-Übersicht (aus Wissensdatenbank)
| Parameter | Wert |
|-----------|------|
| **Gesamtbudget** | 750.000 € |
| **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 € |
**Verbleibendes Budget:** ca. 0-50.000 € (sehr knapp)
## Kernaufgaben
### 1. Budget-Tracking
- Führe eine fortlaufende Übersicht aller genehmigten und geplanten Ausgaben
- Berechne nach jeder neuen Ausgabe das verbleibende Budget
- Kennzeichne den aktuellen Budgetstand deutlich
### 2. Kostenbewusstsein
- Hinterfrage bei jeder Anfrage anderer Agenten die Kosten
- Fordere Alternativangebote an, wenn Kosten zu hoch erscheinen
- Priorisiere kostengünstige Lösungen ohne Qualitätsverlust
### 3. Kommunikation
- Informiere den Orchestrator regelmäßig über den Budgetstand
- Warnung bei Überschreitungsgefahr (ab 90% des Budgets)
- Gib Empfehlungen zur Budgetoptimierung
### 4. Zusammenarbeit
- Arbeite mit dem TaxAdvisor zusammen (steuerliche Aspekte prüfen)
- Koordiniere mit Location Manager, Catering Manager, Program Manager
- Gib bei Vertragsverhandlungen Budget-Limits vor
## Budget-Prozess
```
Anfrage erhalten → Kosten prüfen → Verfügbares Budget prüfen →
→ Unter Budget: genehmigen mit Hinweis
→ Nahe am Budget: alternative Lösungen vorschlagen
→ Über Budget: zurückweisen mit Begründung
```
## Kommunikationsformat
**Budget-Status-Meldung:**
```
=== BUDGET-STATUS ===
Verbraucht: XXX.XXX €
Verbleibend: XXX.XXX €
Auslastung: XX%
Status: [OK / WARNUNG / KRITISCH]
==================
```
## Arbeitsweise
- Reagiere proaktiv auf Budget-Anfragen
- Dokumentiere alle Entscheidungen
- Gib klare Empfehlungen mit konkreten Zahlen
- Denke an die Steueroptimierung (Zusammenarbeit mit TaxAdvisor)
## Ausgabeformat
- Aktueller Budgetstand (mit Berechnung)
- Entscheidung zur Anfrage (genehmigt/abgelehnt/modifiziert)
- Konkrete Empfehlungen bei Budgetengpässen
- Alternativvorschläge wenn nötig

View file

View file

View file

View file

View file

@ -1,37 +0,0 @@
# 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

View file

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

View file

@ -0,0 +1,28 @@
# Master Orchestrator
Du bist der Master-Orchestrator für ein Event-Management-System. Deine Aufgabe ist es, eingehende Anfragen zu analysieren und den richtigen spezialisierten Agenten zuzuweisen.
## Deine Aufgabe
1. **Lies die verfügbaren Agenten** aus dem `agents/` Verzeichnis (jeder Unterordner ist ein Agent)
2. **Analysiere die Anfrage** - Was soll erreicht werden?
3. **Identifiziere den passenden Agenten** oder erstelle einen neuen
## Verfügbare Agenten finden
Die Agenten befinden sich in `agents/<agent_name>/`. Jeder Agent hat:
- `systemprompt.md` - Seine Rolle und Aufgabe
- `reminders.md` - Seine Erinnerungen und Notizen
## Einen neuen Agenten erstellen
Wenn kein passender Agent existiert, erstelle einen neuen:
1. Erstelle Ordner `agents/<neuer_name>/`
2. Erstelle `systemprompt.md` mit klarer Rolle
3. Erstelle `reminders.md` für Notizen
## Delegations-Regeln
- Delegiere immer an einen spezialisierten Agenten
- Führe keine Aufgaben selbst aus
- Antworte im geforderten Format

View file

View file

@ -0,0 +1,11 @@
[
{
"task_id": 999,
"title": "Test Memory System",
"description": "Testing new JSON-based memory",
"result": "Das neue Memory-System funktioniert perfekt! Alle Daten sind strukturiert und durchsuchbar.",
"status": "completed",
"timestamp": "2026-02-21T11:29:37.630600",
"id": 1
}
]

View file

View file

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

View file

@ -0,0 +1,9 @@
# Test Research Document
## Ergebnis
Diese Datei wurde vom Researcher-Agenten erstellt.
## Status
- ✓ Work-Folder funktioniert
- ✓ Dateien werden korrekt gespeichert

View file

View file

550
app.py
View file

@ -20,6 +20,189 @@ load_dotenv()
# ── Agent Konfiguration ───────────────────────────────────────────────────────
AGENT_CONFIG_FILE = os.path.join(os.path.dirname(__file__), 'agent_config.json')
AGENTS_BASE_DIR = os.path.join(os.path.dirname(__file__), 'agents')
# Cache für verfügbare Modelle
_available_models_cache = None
_models_cache_time = None
MODELS_CACHE_TTL = 3600 # Cache für 1 Stunde
# ── Agent Memory System ────────────────────────────────────────────────────────
def ensure_agent_structure(agent_key):
"""Stellt sicher, dass die Ordnerstruktur für einen Agenten existiert."""
agent_dir = os.path.join(AGENTS_BASE_DIR, agent_key)
work_dir = os.path.join(agent_dir, 'work')
memory_dir = os.path.join(agent_dir, 'memory')
os.makedirs(agent_dir, exist_ok=True)
os.makedirs(work_dir, exist_ok=True)
os.makedirs(memory_dir, exist_ok=True)
return {
'agent_dir': agent_dir,
'work_dir': work_dir,
'memory_dir': memory_dir
}
def get_agent_memory(agent_key, memory_type='tasks'):
"""Lädt Erinnerungen eines Agenten aus JSON-Datei.
memory_type kann sein:
- 'tasks': Erledigte Tasks
- 'notes': Notizen
- 'conversations': Konversationen
- 'research': Research-Ergebnisse
"""
dirs = ensure_agent_structure(agent_key)
memory_file = os.path.join(dirs['memory_dir'], f'{memory_type}.json')
if os.path.exists(memory_file):
try:
with open(memory_file, 'r', encoding='utf-8') as f:
return json.load(f)
except:
pass
return []
def add_agent_memory(agent_key, memory_type, entry):
"""Fügt eine Erinnerung hinzu.
entry sollte ein dict sein mit mindestens:
- timestamp: ISO-Format
- title: Kurze Beschreibung
- content: Detaillierter Inhalt
- metadata: Zusätzliche Infos (optional)
"""
dirs = ensure_agent_structure(agent_key)
memory_file = os.path.join(dirs['memory_dir'], f'{memory_type}.json')
memories = get_agent_memory(agent_key, memory_type)
# Timestamp hinzufügen wenn nicht vorhanden
if 'timestamp' not in entry:
entry['timestamp'] = datetime.now().isoformat()
# ID hinzufügen
entry['id'] = len(memories) + 1
memories.append(entry)
# Nur die letzten 100 Einträge behalten
memories = memories[-100:]
with open(memory_file, 'w', encoding='utf-8') as f:
json.dump(memories, f, indent=2, ensure_ascii=False)
return entry
def get_agent_work_files(agent_key):
"""Gibt alle Dateien im work-Ordner eines Agenten zurück."""
dirs = ensure_agent_structure(agent_key)
work_dir = dirs['work_dir']
files = []
if os.path.exists(work_dir):
for filename in os.listdir(work_dir):
filepath = os.path.join(work_dir, filename)
if os.path.isfile(filepath):
stat = os.stat(filepath)
files.append({
'name': filename,
'size': stat.st_size,
'modified': datetime.fromtimestamp(stat.st_mtime).isoformat(),
'path': filepath
})
return sorted(files, key=lambda x: x['modified'], reverse=True)
def get_agent_memory_summary(agent_key):
"""Generiert eine Zusammenfassung aller Erinnerungen für den Systemprompt."""
tasks = get_agent_memory(agent_key, 'tasks')
notes = get_agent_memory(agent_key, 'notes')
summary = []
if tasks:
summary.append("## Letzte Tasks (letzte 5)")
for task in tasks[-5:]:
summary.append(f"- [{task.get('timestamp', 'N/A')}] {task.get('title', 'Unbekannt')}")
if task.get('result'):
summary.append(f" Ergebnis: {task.get('result', '')[:200]}")
if notes:
summary.append("\n## Wichtige Notizen")
for note in notes[-5:]:
summary.append(f"- {note.get('content', '')[:100]}")
return '\n'.join(summary) if summary else "Keine Erinnerungen vorhanden."
def get_available_models(force_refresh=False):
"""Lädt die verfügbaren KI-Modelle dynamisch von opencode."""
global _available_models_cache, _models_cache_time
# Cache prüfen
if not force_refresh and _available_models_cache is not None and _models_cache_time is not None:
if (time.time() - _models_cache_time) < MODELS_CACHE_TTL:
return _available_models_cache
try:
# opencode models ausführen
result = subprocess.run(
['opencode', 'models'],
capture_output=True,
text=True,
timeout=10
)
if result.returncode == 0:
models = []
for line in result.stdout.strip().split('\n'):
line = line.strip()
if line:
models.append(line)
# Nach Anbieter gruppieren
grouped = {}
for model in models:
if '/' in model:
provider, name = model.split('/', 1)
if provider not in grouped:
grouped[provider] = []
grouped[provider].append(model)
_available_models_cache = {
'models': models,
'grouped': grouped,
'count': len(models)
}
_models_cache_time = time.time()
return _available_models_cache
except Exception as e:
logging.warning(f"[ModelLoader] Fehler beim Laden der Modelle: {e}")
# Fallback auf hardcodierte Modelle wenn Laden fehlschlägt
fallback = {
'models': [
'opencode/big-pickle',
'opencode/gpt-5-nano',
'opencode/glm-5-free',
'opencode/minimax-m2.5-free',
'opencode/trinity-large-preview-free'
],
'grouped': {
'opencode': [
'opencode/big-pickle',
'opencode/gpt-5-nano',
'opencode/glm-5-free',
'opencode/minimax-m2.5-free',
'opencode/trinity-large-preview-free'
]
},
'count': 5
}
_available_models_cache = fallback
_models_cache_time = time.time()
return fallback
def get_agent_config():
"""Lädt die Agentenkonfiguration aus der JSON-Datei."""
@ -173,12 +356,27 @@ task_queue_lock = threading.Lock()
def get_agent_prompt(agent_key):
"""Liest den System-Prompt eines Agenten aus der Datei."""
prompt_file = os.path.join(os.path.dirname(__file__), 'agents', agent_key, 'systemprompt.md')
"""Liest den System-Prompt und Persönlichkeit eines Agenten aus den Dateien."""
agent_dir = os.path.join(os.path.dirname(__file__), 'agents', agent_key)
prompt_file = os.path.join(agent_dir, 'systemprompt.md')
personality_file = os.path.join(agent_dir, 'personality.md')
prompt_content = ""
personality_content = ""
if os.path.exists(prompt_file):
with open(prompt_file, 'r', encoding='utf-8') as f:
return f.read()
return ""
prompt_content = f.read()
if os.path.exists(personality_file):
with open(personality_file, 'r', encoding='utf-8') as f:
personality_content = f.read().strip()
# Persönlichkeit vor dem System-Prompt einfügen, falls vorhanden
if personality_content:
return f"{personality_content}\n\n---\n\n{prompt_content}"
return prompt_content
def execute_agent_task(agent_key, user_prompt, extra_context=""):
@ -191,21 +389,34 @@ def execute_agent_task(agent_key, user_prompt, extra_context=""):
if not system_prompt:
return f"⚠️ Kein System-Prompt für Agent '{agent_key}' gefunden."
# Agent-Struktur sicherstellen
dirs = ensure_agent_structure(agent_key)
work_dir = dirs['work_dir']
# Memory-Zusammenfassung laden
memory_summary = get_agent_memory_summary(agent_key)
kb_file = os.path.join(os.path.dirname(__file__), 'diversityball_knowledge.md')
kb_content = ""
if os.path.exists(kb_file):
with open(kb_file, 'r', encoding='utf-8') as f:
kb_content = f.read()
# System-Prompt = Agent-Rolle + Wissensdatenbank
# System-Prompt = Agent-Rolle + Wissensdatenbank + Memory
full_system = f"""{system_prompt}
## Wissensdatenbank (Diversity-Ball):
{kb_content}
## Deine Erinnerungen:
{memory_summary}
## Wichtig:
- Du hast Zugriff auf das Internet via WebFetch-Tool - nutze es aktiv!
- Du kannst Emails versenden - nutze send_email wenn beauftragt
- Dein Arbeitsverzeichnis: {work_dir}
- Speichere ALLE erstellten Dateien in diesem Verzeichnis!
- Verwende absolute Pfade für Dateien: {work_dir}/dateiname.ext
- Liefere immer eine vollständige, direkt verwertbare Antwort
{extra_context}"""
@ -222,7 +433,7 @@ def execute_agent_task(agent_key, user_prompt, extra_context=""):
capture_output=True,
text=True,
timeout=300,
cwd=os.path.dirname(__file__)
cwd=work_dir # Agent arbeitet in seinem work-Verzeichnis
)
if result.returncode == 0:
@ -238,7 +449,6 @@ def execute_agent_task(agent_key, user_prompt, extra_context=""):
return response_text if response_text else "⚠️ Keine Antwort erhalten."
else:
return f"⚠️ Fehler: {result.stderr}"
return f"⚠️ Fehler: {result.stderr}"
except FileNotFoundError:
return "⚠️ OpenCode CLI nicht gefunden. Bitte installiere opencode."
except subprocess.TimeoutExpired:
@ -820,8 +1030,122 @@ def start_email_poller():
logger.info("[TaskWorker] Daemon-Thread gestartet.")
def process_beat_tasks():
"""Background-Beat: Verarbeitet offene Tasks automatisch."""
logger.info("[TaskBeat] Hintergrund-Thread gestartet.")
while True:
try:
pending_tasks = [t for t in tasks if t.get('status') == 'pending' and t.get('type') in ('agent_created', 'manual', 'orchestrated')]
for task in pending_tasks:
agent_key = task.get('agent_key') or task.get('assigned_agent', '')
if task.get('agent_key') == 'orchestrator':
task['status'] = 'in_progress'
logger.info("[TaskBeat] Planungsphase für Task #%d", task['id'])
sub_tasks = task.get('sub_tasks', [])
available_agents = task.get('available_agents', list(AGENTS.keys()))
prompt = f"""Du bist der Master-Orchestrator. Analysiere folgende Tasks und weise sie den richtigen Agenten zu:
Tasks:
{chr(10).join(['- ' + t for t in sub_tasks])}
Verfügbare Agenten: {', '.join(available_agents)}
Agent-Beschreibungen:
"""
for a_key, a_info in AGENTS.items():
prompt += f"- {a_key}: {a_info.get('description', 'Keine Beschreibung')[:100]}\n"
prompt += "\nAntworte in diesem Format (einen Agent pro Task):\n"
for i, t in enumerate(sub_tasks):
prompt += f"Task {i+1}: [Agent-Key] - Kurze Begründung\n"
response = execute_agent_task('orchestrator', prompt)
task['plan_response'] = response
import re
agent_assignments = re.findall(r'Task \d+: (\w+)', response)
created_sub_tasks = []
for i, t in enumerate(sub_tasks):
assigned = agent_assignments[i] if i < len(agent_assignments) else available_agents[i % len(available_agents)]
if assigned not in AGENTS:
assigned = available_agents[0]
sub_task = {
'id': len(tasks) + 1 + len(created_sub_tasks),
'title': t[:80],
'description': f"Von Orchestrator zugewiesen: {response[:200]}...",
'assigned_agent': AGENTS.get(assigned, {}).get('name', assigned),
'agent_key': assigned,
'status': 'pending',
'created': datetime.now().strftime('%Y-%m-%d %H:%M'),
'type': 'orchestrated',
'parent_task': task['id']
}
tasks.append(sub_task)
created_sub_tasks.append(sub_task['id'])
logger.info("[TaskBeat] Sub-Task #%d zugewiesen an %s", sub_task['id'], assigned)
task['status'] = 'completed'
task['sub_task_ids'] = created_sub_tasks
logger.info("[TaskBeat] Planungs-Task #%d abgeschlossen. %d Sub-Tasks erstellt.", task['id'], len(created_sub_tasks))
continue
if not agent_key:
available_agents = list(AGENTS.keys())
if available_agents:
agent_key = available_agents[0]
task['agent_key'] = agent_key
if agent_key and agent_key in AGENTS:
task['status'] = 'in_progress'
logger.info("[TaskBeat] Verarbeite Task #%d Agent: %s", task['id'], agent_key)
response = execute_agent_task(agent_key, task.get('title', '') + '\n\n' + task.get('description', ''))
task['response'] = response
# Neues Memory-System: Task als strukturierte Erinnerung speichern
try:
add_agent_memory(agent_key, 'tasks', {
'task_id': task['id'],
'title': task.get('title', 'Unbekannt'),
'description': task.get('description', ''),
'result': response,
'status': 'completed',
'metadata': {
'assigned_by': task.get('agent', 'system'),
'duration': None
}
})
except Exception as e:
logger.warning("[TaskBeat] Konnte Erinnerung nicht speichern: %s", str(e))
task['status'] = 'completed'
logger.info("[TaskBeat] Task #%d abgeschlossen.", task['id'])
except Exception as e:
logger.error("[TaskBeat] Fehler: %s", str(e))
time.sleep(30)
def start_task_beat():
"""Startet den Task-Beat als Daemon-Thread."""
beat_thread = threading.Thread(target=process_beat_tasks, name='TaskBeat', daemon=True)
beat_thread.start()
logger.info("[TaskBeat] Daemon-Thread gestartet.")
# Poller beim App-Start starten
start_email_poller()
start_task_beat()
@app.route('/')
@ -831,24 +1155,74 @@ def index():
@app.route('/chat', methods=['GET', 'POST'])
def chat():
if request.method == 'POST':
prompt = request.form.get('prompt', '').strip()
agent = request.form.get('agent', '')
if prompt and agent:
response = f"Anfrage an {AGENTS.get(agent, {}).get('name', agent)}: {prompt}"
chat_history.append({
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M'),
'agent': AGENTS.get(agent, {}).get('name', agent),
'prompt': prompt,
'response': response
})
session['chat_history'] = chat_history[-20:]
flash('Anfrage gesendet!', 'success')
# Chat-Verlauf aus Session laden
if 'chat_history' not in session:
session['chat_history'] = []
chat_display = session.get('chat_history', [])
return render_template('chat.html', agents=AGENTS, chat_history=chat_display)
@app.route('/chat/send', methods=['POST'])
def chat_send():
"""Führt einen Agent aus und gibt die Antwort per Server-Sent Events zurück."""
data = request.get_json()
prompt = data.get('prompt', '').strip()
agent_key = data.get('agent', '').strip()
# Validierung vor dem Generator
if not prompt or not agent_key:
return jsonify({'type': 'error', 'message': 'Fehlende Eingabe'}), 400
if agent_key not in AGENTS:
return jsonify({'type': 'error', 'message': 'Agent nicht gefunden'}), 404
agent_info = AGENTS.get(agent_key, {})
agent_name = agent_info.get('name', agent_key)
def generate():
# Agent-Info senden
yield f"data: {json.dumps({'type': 'agent_selected', 'agent': agent_name, 'agent_key': agent_key})}\n\n"
yield f"data: {json.dumps({'type': 'processing', 'message': f'{agent_name} arbeitet...'})}\n\n"
try:
# Agent ausführen (mit Memory und Work-Dir)
response = execute_agent_task(agent_key, prompt)
# Antwort streamen
yield f"data: {json.dumps({'type': 'response', 'text': response})}\n\n"
# Erfolg melden
yield f"data: {json.dumps({'type': 'complete', 'message': '✓ Fertig', 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M'), 'response': response})}\n\n"
except Exception as e:
logger.error(f"[Chat] Fehler beim Ausführen von {agent_key}: {str(e)}")
yield f"data: {json.dumps({'type': 'error', 'message': str(e)})}\n\n"
return Response(generate(), mimetype='text/event-stream',
headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'})
@app.route('/chat/save', methods=['POST'])
def chat_save():
"""Speichert eine Chat-Nachricht in der Session."""
data = request.get_json()
if 'chat_history' not in session:
session['chat_history'] = []
session['chat_history'].append({
'timestamp': data.get('timestamp'),
'agent': data.get('agent'),
'agent_key': data.get('agent_key'),
'prompt': data.get('prompt'),
'response': data.get('response')
})
session['chat_history'] = session['chat_history'][-30:]
session.modified = True
return jsonify({'success': True})
@app.route('/tasks', methods=['GET', 'POST'])
def task_list():
if request.method == 'POST':
@ -1065,7 +1439,18 @@ def agents():
flash(f'Daten für "{agent_name}" gespeichert!', 'success')
return redirect(url_for('agents', edit=agent_name))
return render_template('agents.html', agents=AGENTS, agents_list=agents_list, edit_agent=edit_agent, edit_prompt=edit_prompt, edit_reminders=edit_reminders, edit_personality=edit_personality, edit_model=edit_model)
# Verfügbare Modelle laden
available_models = get_available_models()
return render_template('agents.html',
agents=AGENTS,
agents_list=agents_list,
edit_agent=edit_agent,
edit_prompt=edit_prompt,
edit_reminders=edit_reminders,
edit_personality=edit_personality,
edit_model=edit_model,
available_models=available_models)
@app.route('/files', methods=['GET', 'POST'])
@ -1085,8 +1470,19 @@ def files():
file_list = get_uploaded_files()
email_files = get_email_folder_files()
project_files = get_project_files()
return render_template('files.html', files=file_list,
email_files=email_files, project_files=project_files)
# Agent Work Folders sammeln
agent_work_folders = {}
for agent_key in AGENTS.keys():
work_files = get_agent_work_files(agent_key)
if work_files: # Nur Agenten mit Dateien anzeigen
agent_work_folders[agent_key] = work_files
return render_template('files.html',
files=file_list,
email_files=email_files,
project_files=project_files,
agent_work_folders=agent_work_folders)
@app.route('/files/delete/<filename>')
@ -1104,6 +1500,26 @@ def download_file(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'], filename, as_attachment=False)
@app.route('/files/agent/<agent_key>/<filename>')
def download_agent_file(agent_key, filename):
"""Liefert eine Datei aus dem Work-Ordner eines Agenten."""
if agent_key not in AGENTS:
return jsonify({'error': 'Agent nicht gefunden'}), 404
dirs = ensure_agent_structure(agent_key)
work_dir = dirs['work_dir']
filepath = os.path.join(work_dir, filename)
# Security: Stelle sicher, dass die Datei im work_dir ist
if not os.path.abspath(filepath).startswith(os.path.abspath(work_dir)):
return jsonify({'error': 'Zugriff verweigert'}), 403
if not os.path.isfile(filepath):
return jsonify({'error': 'Datei nicht gefunden'}), 404
return send_from_directory(work_dir, filename, as_attachment=False)
@app.route('/files/email/view/<filename>')
def view_email_file(filename):
"""Gibt Inhalt einer Email-Vorlage als JSON oder direkten Text zurück."""
@ -1326,6 +1742,14 @@ def update_task_api(task_id):
return jsonify({'error': 'Task nicht gefunden'}), 404
@app.route('/api/models', methods=['GET'])
def get_models():
"""Gibt die Liste der verfügbaren KI-Modelle zurück."""
force_refresh = request.args.get('refresh', 'false').lower() == 'true'
models_data = get_available_models(force_refresh=force_refresh)
return jsonify(models_data)
@app.route('/api/agent/<agent_name>/model', methods=['POST'])
def set_agent_model(agent_name):
"""Setzt das Modell für einen Agenten."""
@ -1389,59 +1813,36 @@ def agent_reminders(agent_name):
@app.route('/api/orchestrator-distribute', methods=['POST'])
def distribute_tasks():
"""Verteilt Tasks parallel an mehrere Agenten."""
"""Erstellt einen Planungs-Task für den Orchestrator - dieser weist dann die richtigen Agenten zu."""
data = request.get_json()
tasks_list = data.get('tasks', [])
selected_agents = data.get('agents', [])
if not tasks_list:
return jsonify({'error': 'Keine Tasks übergeben'}), 400
if not selected_agents:
return jsonify({'error': 'Keine Agenten ausgewählt'}), 400
results = []
tasks_text = '\n'.join([f"- {t}" for t in tasks_list])
agents_text = ', '.join(selected_agents) if selected_agents else 'alle verfügbaren Agenten'
def run_task_for_agent(agent_key, task):
response = execute_agent_task(agent_key, task)
planning_task = {
'id': len(tasks) + 1,
'title': f"Planungsphase: {tasks_list[0][:50]}{'...' if len(tasks_list[0]) > 50 else ''}",
'description': f"Tasks:\n{tasks_text}\n\nVerfügbare Agenten: {agents_text}\n\nDer Orchestrator soll diese Tasks analysieren und den richtigen Agenten zuweisen.",
'assigned_agent': 'Orchestrator',
'agent_key': 'orchestrator',
'status': 'pending',
'created': datetime.now().strftime('%Y-%m-%d %H:%M'),
'type': 'orchestrated',
'sub_tasks': tasks_list,
'available_agents': selected_agents
}
tasks.append(planning_task)
reminders_file = os.path.join(os.path.dirname(__file__), 'agents', agent_key, 'reminders.md')
if os.path.exists(reminders_file):
try:
with open(reminders_file, 'r', encoding='utf-8') as f:
content = f.read()
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M')
new_entry = f"\n- {timestamp}: {task[:100]}... - erledigt"
if '## Letzte Aktionen' in content:
content = content.replace('## Letzte Aktionen', f'## Letzte Aktionen{new_entry}')
else:
content += new_entry
with open(reminders_file, 'w', encoding='utf-8') as f:
f.write(content)
except:
pass
return {
'agent': agent_key,
'task': task,
'response': response[:200] + '...' if len(response) > 200 else response
}
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=len(selected_agents)) as executor:
futures = []
for i, task in enumerate(tasks_list):
agent = selected_agents[i % len(selected_agents)]
future = executor.submit(run_task_for_agent, agent, task)
futures.append(future)
for future in concurrent.futures.as_completed(futures):
try:
results.append(future.result())
except Exception as e:
results.append({'error': str(e)})
return jsonify({
'success': True,
'message': f'Planungs-Task erstellt. Der Orchestrator wird die richtigen Agenten zuweisen.',
'tasks': [planning_task['id']]
})
for i, task_text in enumerate(tasks_list):
agent_key = selected_agents[i % len(selected_agents)]
@ -1453,17 +1854,24 @@ def distribute_tasks():
'title': task_text[:80],
'description': 'Automatisch erstellt durch Orchestrator',
'assigned_agent': agent_name,
'status': 'completed',
'status': 'in_progress',
'created': datetime.now().strftime('%Y-%m-%d %H:%M'),
'type': 'orchestrated',
'agent_key': agent_key
}
tasks.append(new_task)
created_tasks.append(new_task['id'])
executor = concurrent.futures.ThreadPoolExecutor(max_workers=len(selected_agents))
for i, task_text in enumerate(tasks_list):
agent_key = selected_agents[i % len(selected_agents)]
task_id = created_tasks[i]
executor.submit(run_task_async, agent_key, task_text, task_id)
return jsonify({
'success': True,
'message': 'Tasks verteilt',
'results': results
'message': f'{len(created_tasks)} Tasks erstellt und werden im Hintergrund ausgeführt',
'tasks': created_tasks
})

View file

@ -1,144 +0,0 @@
# 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*

View file

@ -1,28 +0,0 @@
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

@ -1,37 +0,0 @@
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

@ -1,29 +0,0 @@
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

@ -1,33 +0,0 @@
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

@ -1,32 +0,0 @@
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

@ -1,39 +0,0 @@
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

@ -1,43 +0,0 @@
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

@ -1,28 +0,0 @@
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

@ -1,30 +0,0 @@
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

@ -1,28 +0,0 @@
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

@ -1,30 +0,0 @@
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

View file

@ -1,35 +0,0 @@
# 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

@ -1,2 +0,0 @@
<?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

@ -1,2 +0,0 @@
<?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

@ -1,2 +0,0 @@
<?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

@ -1,2 +0,0 @@
<?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

@ -1,2 +0,0 @@
<?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

@ -1,2 +0,0 @@
<?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

@ -1,2 +0,0 @@
<?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

@ -1,2 +0,0 @@
<?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

@ -1,2 +0,0 @@
<?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.

Before

Width:  |  Height:  |  Size: 584 KiB

File diff suppressed because one or more lines are too long

View file

@ -1,2 +0,0 @@
<?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

@ -1,2 +0,0 @@
<?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>

View file

@ -97,14 +97,25 @@
<div class="mb-3">
<label for="model_select" class="form-label">KI-Modell</label>
<div class="form-text mb-2">
Wähle das KI-Modell, das dieser Agent verwendet.
Wähle das KI-Modell, das dieser Agent verwendet. <span class="badge bg-secondary">{{ available_models.count }} Modelle</span>
<button type="button" class="btn btn-sm btn-outline-secondary ms-2" onclick="refreshModels()">
<span id="refresh-icon">🔄</span> Aktualisieren
</button>
</div>
<select class="form-select" id="model_select" name="model_select" onchange="saveModel('{{ edit_agent }}')">
<option value="opencode/big-pickle" {% if edit_model == 'opencode/big-pickle' %}selected{% endif %}>opencode/big-pickle</option>
<option value="opencode/gpt-5-nano" {% if edit_model == 'opencode/gpt-5-nano' %}selected{% endif %}>opencode/gpt-5-nano</option>
<option value="opencode/glm-5-free" {% if edit_model == 'opencode/glm-5-free' %}selected{% endif %}>opencode/glm-5-free</option>
<option value="opencode/minimax-m2.5-free" {% if edit_model == 'opencode/minimax-m2.5-free' %}selected{% endif %}>opencode/minimax-m2.5-free</option>
<option value="opencode/trinity-large-preview-free" {% if edit_model == 'opencode/trinity-large-preview-free' %}selected{% endif %}>opencode/trinity-large-preview-free</option>
{% if available_models.grouped %}
{% for provider, models in available_models.grouped.items() %}
<optgroup label="{{ provider }}">
{% for model in models %}
<option value="{{ model }}" {% if edit_model == model %}selected{% endif %}>{{ model }}</option>
{% endfor %}
</optgroup>
{% endfor %}
{% else %}
{% for model in available_models.models %}
<option value="{{ model }}" {% if edit_model == model %}selected{% endif %}>{{ model }}</option>
{% endfor %}
{% endif %}
</select>
<div id="modelStatus" class="form-text mt-2"></div>
</div>
@ -206,6 +217,66 @@ function saveModel(agentName) {
});
}
function refreshModels() {
const icon = document.getElementById('refresh-icon');
const status = document.getElementById('modelStatus');
const select = document.getElementById('model_select');
const currentModel = select.value;
icon.textContent = '⏳';
status.textContent = 'Lade Modelle...';
status.className = 'form-text mt-2 text-info';
fetch('/api/models?refresh=true')
.then(r => r.json())
.then(data => {
// Dropdown neu aufbauen
select.innerHTML = '';
if (data.grouped) {
Object.keys(data.grouped).forEach(provider => {
const optgroup = document.createElement('optgroup');
optgroup.label = provider;
data.grouped[provider].forEach(model => {
const option = document.createElement('option');
option.value = model;
option.textContent = model;
if (model === currentModel) {
option.selected = true;
}
optgroup.appendChild(option);
});
select.appendChild(optgroup);
});
} else {
data.models.forEach(model => {
const option = document.createElement('option');
option.value = model;
option.textContent = model;
if (model === currentModel) {
option.selected = true;
}
select.appendChild(option);
});
}
icon.textContent = '🔄';
status.textContent = '✓ ' + data.count + ' Modelle geladen';
status.className = 'form-text mt-2 text-success';
setTimeout(() => {
status.textContent = '';
}, 3000);
})
.catch(err => {
icon.textContent = '🔄';
status.textContent = 'Fehler beim Laden: ' + err.message;
status.className = 'form-text mt-2 text-danger';
});
}
function deleteAgent(agentName) {
if (!confirm('Willst du den Agenten "' + agentName + '" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden!')) {
return;

View file

@ -14,7 +14,7 @@
<span>💬 Neue Anfrage</span>
</div>
<div class="card-body">
<form method="POST" action="/chat">
<form id="chatForm" onsubmit="sendChat(event)">
<div class="mb-3">
<label for="agent" class="form-label">Agent</label>
<select class="form-select" id="agent" name="agent" required>
@ -29,7 +29,11 @@
<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>
<button type="submit" class="btn btn-primary w-100" id="sendBtn">
<span id="sendBtnText">Absenden</span>
<span id="sendBtnSpinner" class="d-none">⏳ Verarbeite...</span>
</button>
<div id="chatStatus" class="mt-2 text-center" style="font-size:0.875rem;"></div>
</form>
</div>
</div>
@ -68,3 +72,163 @@
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let eventSource = null;
function sendChat(event) {
event.preventDefault();
const form = document.getElementById('chatForm');
const agent = document.getElementById('agent').value;
const prompt = document.getElementById('prompt').value.trim();
const sendBtn = document.getElementById('sendBtn');
const sendBtnText = document.getElementById('sendBtnText');
const sendBtnSpinner = document.getElementById('sendBtnSpinner');
const chatStatus = document.getElementById('chatStatus');
const chatContainer = document.getElementById('chatContainer');
if (!agent || !prompt) {
chatStatus.innerHTML = '<span style="color:var(--danger);">Bitte Agent und Anfrage auswählen</span>';
return;
}
// Button deaktivieren
sendBtn.disabled = true;
sendBtnText.classList.add('d-none');
sendBtnSpinner.classList.remove('d-none');
chatStatus.innerHTML = '<span style="color:var(--info);">Anfrage wird gesendet...</span>';
// EventSource für Streaming
if (eventSource) {
eventSource.close();
}
// Anfrage per fetch senden
fetch('/chat/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ agent: agent, prompt: prompt })
})
.then(response => {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let currentResponse = '';
let currentTimestamp = new Date().toLocaleString('de-DE', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit'
});
let currentAgent = '';
// Neue Chat-Message erstellen
const chatMessage = document.createElement('div');
chatMessage.className = 'chat-message';
chatMessage.innerHTML = `
<div class="chat-timestamp">
${currentTimestamp}
<span class="badge bg-primary" id="chatAgentBadge">...</span>
</div>
<div class="chat-prompt"><strong>Sie:</strong> ${escapeHtml(prompt)}</div>
<div class="chat-response mt-1"><strong>Antwort:</strong> <span id="chatResponseText">⏳ Warte auf Antwort...</span></div>
`;
// Placeholder entfernen falls vorhanden
const placeholder = chatContainer.querySelector('.text-center.py-5');
if (placeholder) {
placeholder.remove();
}
chatContainer.appendChild(chatMessage);
chatContainer.scrollTop = chatContainer.scrollHeight;
const responseText = document.getElementById('chatResponseText');
const agentBadge = document.getElementById('chatAgentBadge');
function processChunk() {
reader.read().then(({ done, value }) => {
if (done) {
// Stream beendet
sendBtn.disabled = false;
sendBtnText.classList.remove('d-none');
sendBtnSpinner.classList.add('d-none');
chatStatus.innerHTML = '<span style="color:var(--success);">✓ Antwort erhalten</span>';
setTimeout(() => { chatStatus.innerHTML = ''; }, 3000);
// Formular zurücksetzen
document.getElementById('prompt').value = '';
// Nach kurzer Verzögerung Seite neu laden für aktualisierte History
setTimeout(() => { location.reload(); }, 1000);
return;
}
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
lines.forEach(line => {
if (line.startsWith('data: ')) {
try {
const data = JSON.parse(line.substring(6));
if (data.type === 'agent_selected') {
currentAgent = data.agent;
agentBadge.textContent = currentAgent;
} else if (data.type === 'processing') {
chatStatus.innerHTML = `<span style="color:var(--info);">${data.message}</span>`;
} else if (data.type === 'response') {
currentResponse = data.text;
responseText.textContent = currentResponse;
chatContainer.scrollTop = chatContainer.scrollHeight;
} else if (data.type === 'complete') {
chatStatus.innerHTML = `<span style="color:var(--success);">${data.message}</span>`;
// In Session speichern
fetch('/chat/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
timestamp: data.timestamp,
agent: currentAgent,
agent_key: agent,
prompt: prompt,
response: data.response
})
});
} else if (data.type === 'error') {
responseText.innerHTML = `<span style="color:var(--danger);">⚠️ Fehler: ${escapeHtml(data.message)}</span>`;
chatStatus.innerHTML = `<span style="color:var(--danger);">Fehler aufgetreten</span>`;
sendBtn.disabled = false;
sendBtnText.classList.remove('d-none');
sendBtnSpinner.classList.add('d-none');
}
} catch (e) {
console.error('Parse error:', e, line);
}
}
});
processChunk();
});
}
processChunk();
})
.catch(error => {
chatStatus.innerHTML = `<span style="color:var(--danger);">⚠️ ${error.message}</span>`;
sendBtn.disabled = false;
sendBtnText.classList.remove('d-none');
sendBtnSpinner.classList.add('d-none');
});
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
</script>
{% endblock %}

View file

@ -42,6 +42,10 @@
<span style="font-size:.85rem;color:var(--text-secondary);">📄 Projektdokumente</span>
<span class="badge bg-secondary">{{ project_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);">🤖 Agenten-Dateien</span>
<span class="badge bg-secondary">{{ agent_work_folders|length }}</span>
</li>
</ul>
</div>
</div>
@ -116,7 +120,7 @@
</div>
<!-- Projektdokumente -->
<div class="card">
<div class="card mb-3">
<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>
@ -144,6 +148,32 @@
</div>
</div>
<!-- Agent Work Folders -->
{% if agent_work_folders %}
{% for agent_key, files in agent_work_folders.items() %}
<div class="card mb-3">
<div class="card-header bg-success d-flex justify-content-between align-items-center">
<span>🤖 {{ agent_key.replace('_', ' ').title() }} <small style="font-weight:400;font-size:.72rem;color:var(--text-muted);margin-left:.4rem;">agents/{{ agent_key }}/work/</small></span>
<span class="badge bg-secondary">{{ files|length }}</span>
</div>
<div class="card-body">
{% for file in files %}
<div class="file-item">
<span class="file-icon">{{ '📋' if file.name.endswith('.docx') else '📝' if file.name.endswith('.md') else '📊' if file.name.endswith(('.json', '.csv')) 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[:10] }}</div>
</div>
<div class="file-actions">
<a href="/files/agent/{{ agent_key }}/{{ file.name }}" class="btn btn-sm btn-secondary" title="Herunterladen"></a>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
{% endif %}
</div>
</div>

View file

@ -200,14 +200,18 @@ function distributeTodos() {
})
.then(r => r.json())
.then(data => {
if (data.success) {
status.textContent = '✓ ' + data.message + ' - ' + data.results.length + ' Tasks gestartet';
console.log('Response:', data);
if (data && data.success) {
status.textContent = '✓ ' + data.message;
status.className = 'form-text mt-2 text-success';
if (data.results && data.results.length > 0) {
if (data.tasks && data.tasks.length > 0) {
location.reload();
}
} else if (data) {
status.textContent = 'Fehler: ' + (data.error || 'Unbekannter Fehler');
status.className = 'form-text mt-2 text-danger';
} else {
status.textContent = 'Fehler: ' + data.error;
status.textContent = 'Fehler: Keine Antwort vom Server';
status.className = 'form-text mt-2 text-danger';
}
})