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:
parent
84b2fe3dd7
commit
93eb8c6d47
83 changed files with 1692 additions and 1517 deletions
182
Event-Plan_Diversity-Ball_5-September-2026.md
Normal file
182
Event-Plan_Diversity-Ball_5-September-2026.md
Normal 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*
|
||||
131
FILE_CHANGES.txt
131
FILE_CHANGES.txt
|
|
@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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:00–02: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 (4–8 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: 4–8 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
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
```
|
||||
|
|
|
|||
2
agents/budget_manager/personality.md
Normal file
2
agents/budget_manager/personality.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Du bist trocken, steif und hörst auf den Namen Hans-Ruedi
|
||||
|
||||
79
agents/budget_manager/systemprompt.md
Normal file
79
agents/budget_manager/systemprompt.md
Normal 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
|
||||
0
agents/catering_manager/personality.md
Normal file
0
agents/catering_manager/personality.md
Normal file
0
agents/document_editor/personality.md
Normal file
0
agents/document_editor/personality.md
Normal file
0
agents/location_manager/personality.md
Normal file
0
agents/location_manager/personality.md
Normal file
0
agents/musik_rechte_advisor/personality.md
Normal file
0
agents/musik_rechte_advisor/personality.md
Normal file
0
agents/negotiator/personality.md
Normal file
0
agents/negotiator/personality.md
Normal 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`
|
||||
0
agents/orchestrator/personality.md
Normal file
0
agents/orchestrator/personality.md
Normal file
10
agents/orchestrator/reminders.md
Normal file
10
agents/orchestrator/reminders.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Erinnerungen - Orchestrator
|
||||
|
||||
## Aktuelle Tasks
|
||||
-
|
||||
|
||||
## Notizen
|
||||
-
|
||||
|
||||
## Letzte Aktionen
|
||||
-
|
||||
28
agents/orchestrator/systemprompt.md
Normal file
28
agents/orchestrator/systemprompt.md
Normal 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
|
||||
0
agents/program_manager/personality.md
Normal file
0
agents/program_manager/personality.md
Normal file
11
agents/researcher/memory/tasks.json
Normal file
11
agents/researcher/memory/tasks.json
Normal 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
|
||||
}
|
||||
]
|
||||
0
agents/researcher/personality.md
Normal file
0
agents/researcher/personality.md
Normal 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
|
||||
-
|
||||
|
|
|
|||
9
agents/researcher/work/test_research.md
Normal file
9
agents/researcher/work/test_research.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Test Research Document
|
||||
|
||||
## Ergebnis
|
||||
Diese Datei wurde vom Researcher-Agenten erstellt.
|
||||
|
||||
## Status
|
||||
- ✓ Work-Folder funktioniert
|
||||
- ✓ Dateien werden korrekt gespeichert
|
||||
|
||||
0
agents/social_media_manager/personality.md
Normal file
0
agents/social_media_manager/personality.md
Normal file
0
agents/tax_advisor/personality.md
Normal file
0
agents/tax_advisor/personality.md
Normal file
0
agents/zusammenfasser/personality.md
Normal file
0
agents/zusammenfasser/personality.md
Normal file
548
app.py
548
app.py
|
|
@ -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)
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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*
|
||||
|
|
@ -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]
|
||||
|
|
@ -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]
|
||||
|
|
@ -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]
|
||||
|
|
@ -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]
|
||||
|
|
@ -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]
|
||||
|
|
@ -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]
|
||||
|
|
@ -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]
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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/`
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue