# -*- coding: utf-8 -*- """ Grundlegende Event-Datenklassen. Definiert häufig verwendete Events im Trixy-System. """ from dataclasses import dataclass, field from typing import Any from trixy_core.events.event_data.base import EventData @dataclass class SatelliteConnected(EventData): """Event wenn ein Satellite sich verbindet.""" satellite_id: str = "" room: str = "" alias: str = "" mac_address: str = "" ip_address: str = "" @dataclass class SatelliteDisconnected(EventData): """Event wenn ein Satellite die Verbindung trennt.""" satellite_id: str = "" room: str = "" alias: str = "" reason: str = "" @dataclass class MusicSocketConnected(EventData): """Event wenn der Music-Socket eines Satellites verbunden ist.""" satellite_id: str = "" alias: str = "" room: str = "" @dataclass class WakewordReceived(EventData): """Event wenn ein Wakeword erkannt wurde.""" satellite_id: str = "" wakeword_model: str = "" confidence: float = 0.0 audio_level: float = 0.0 @dataclass class RawAudioInputReceived(EventData): """Event wenn rohe Audiodaten empfangen wurden.""" satellite_id: str = "" audio_data: bytes = field(default=b"", repr=False) sample_rate: int = 16000 duration_seconds: float = 0.0 @dataclass class SpeechRecognized(EventData): """ Event wenn Sprache erkannt wurde (STT-Ergebnis). Attributes: satellite_id: ID des Satellites room_id: Raum-ID des Satellites text: Erkannter Text confidence: Konfidenz der Erkennung (0.0-1.0) language: Sprache des Textes is_final: Ist dies das finale Ergebnis? (vs. Zwischen-Ergebnis) source: Quelle der Eingabe ("stt", "keyboard", etc.) alternatives: Alternative Erkennungen """ satellite_id: str = "" room_id: str = "" text: str = "" confidence: float = 0.0 language: str = "de" is_final: bool = True source: str = "stt" # "stt", "keyboard", "api" alternatives: list[str] = field(default_factory=list) @dataclass class IntentReceived(EventData): """ Event wenn ein Intent erkannt wurde. Wird vom NLP-Provider emittiert nachdem Text verarbeitet wurde. Intent-Handler Plugins reagieren auf dieses Event. Flow: 1. NLP emittiert IntentReceived 2. Intent-Handler Plugin verarbeitet und emittiert IntentHandled 3. Falls keine Antwort: CreateOutputText wird emittiert 4. Response Generator erstellt Text und emittiert OutputTextCreated 5. TTS Plugin reagiert auf OutputTextCreated """ satellite_id: str = "" intent: str = "" confidence: float = 0.0 slots: dict[str, Any] = field(default_factory=dict) original_text: str = "" session_id: str = "" room_id: str = "" wakeword_type: str = "custom" # "custom" oder "system_command" is_authenticated: bool = False # Admin-Authentifizierung language: str = "de" metadata: dict[str, Any] = field(default_factory=dict) @dataclass class IntentHandled(EventData): """ Event wenn ein Intent-Handler die Verarbeitung abgeschlossen hat. Wird vom Intent-Handler emittiert nach der Ausführung. Enthält das Ergebnis der Aktion. Attributes: satellite_id: ID des Satellites session_id: Konversations-Session-ID intent: Verarbeiteter Intent success: War die Verarbeitung erfolgreich? response_text: Optionaler Antworttext (falls Handler selbst antwortet) data: Ergebnisdaten der Aktion error: Fehlermeldung falls nicht erfolgreich needs_followup: Erwartet Rückfrage/Fortsetzung? suppress_response: Keine Antwort generieren? """ satellite_id: str = "" session_id: str = "" room_id: str = "" intent: str = "" original_text: str = "" slots: dict[str, Any] = field(default_factory=dict) success: bool = True response_text: str = "" # Falls Handler eigene Antwort liefert data: dict[str, Any] = field(default_factory=dict) # Ergebnisdaten error: str = "" needs_followup: bool = False # Konversation fortsetzen? followup_prompt: str = "" # Rückfrage-Text suppress_response: bool = False # Keine Antwort generieren @dataclass class ConversationStarted(EventData): """Event wenn eine Konversation beginnt.""" satellite_id: str = "" conversation_id: str = "" @dataclass class ConversationEnded(EventData): """Event wenn eine Konversation endet.""" satellite_id: str = "" conversation_id: str = "" reason: str = "" # timeout, user_ended, error, completed @dataclass class PluginLoaded(EventData): """Event wenn ein Plugin geladen wurde.""" plugin_name: str = "" plugin_version: str = "" @dataclass class PluginUnloaded(EventData): """Event wenn ein Plugin entladen wurde.""" plugin_name: str = "" reason: str = "" @dataclass class SystemShutdown(EventData): """Event vor dem Herunterfahren des Systems.""" reason: str = "" force: bool = False @dataclass class TTSRequest(EventData): """Event für TTS-Anfrage.""" request_id: str = "" satellite_id: str = "" text: str = "" voice: str = "default" speed: float = 1.0 volume: float = 1.0 @dataclass class TTSCompleted(EventData): """Event wenn TTS abgeschlossen ist.""" request_id: str = "" satellite_id: str = "" success: bool = True audio_duration: float = 0.0 error: str = "" @dataclass class StreamStart(EventData): """Event zum Starten eines Audio-Streams.""" satellite_id: str = "" source: str = "" volume: float = 1.0 stream_type: str = "music" # "music", "audio", "tts" @dataclass class StreamStop(EventData): """Event zum Stoppen eines Audio-Streams.""" satellite_id: str = "" stream_type: str = "all" # "music", "audio", "tts", "all" @dataclass class ProcessingResult(EventData): """Event mit Verarbeitungsergebnis (für Standalone-Modus).""" session_id: str = "" success: bool = True text: str = "" intent: str = "" response_text: str = "" error: str = "" @dataclass class CreateOutputText(EventData): """ Event zur Generierung von Antworttext. Wird emittiert nach IntentHandled wenn: - Handler keine response_text geliefert hat - suppress_response nicht gesetzt ist Response-Generator Plugin reagiert und emittiert OutputTextCreated. Attributes: satellite_id: ID des Satellites session_id: Konversations-Session-ID room_id: Raum-ID intent: Erkannter Intent original_text: Ursprüngliche Benutzeranfrage slots: Extrahierte Slot-Werte handler_data: Ergebnisdaten des Intent-Handlers handler_success: War der Handler erfolgreich? handler_error: Fehlermeldung des Handlers language: Sprache für Antwort """ satellite_id: str = "" session_id: str = "" room_id: str = "" intent: str = "" original_text: str = "" slots: dict[str, Any] = field(default_factory=dict) handler_data: dict[str, Any] = field(default_factory=dict) handler_success: bool = True handler_error: str = "" language: str = "de" @dataclass class OutputTextCreated(EventData): """ Event wenn Antworttext generiert wurde. Wird vom Response-Generator emittiert. TTS Plugin reagiert darauf und spricht den Text. Conversation Service aktualisiert die Session. Attributes: satellite_id: ID des Satellites session_id: Konversations-Session-ID room_id: Raum-ID text: Generierter Antworttext intent: Ursprünglicher Intent is_followup: Ist dies eine Rückfrage? expects_response: Erwartet Benutzerantwort? """ satellite_id: str = "" session_id: str = "" room_id: str = "" text: str = "" intent: str = "" is_followup: bool = False expects_response: bool = False @dataclass class FollowUpExpected(EventData): """ Event wenn eine Rückfrage erwartet wird. Wird emittiert nachdem TTS abgeschlossen ist und der Benutzer antworten soll. Der Client soll ohne Wakeword-Erkennung direkt Audio streamen. Flow: 1. Handler setzt needs_followup=True 2. IntentDispatcher emittiert output_text_created mit expects_response=True 3. TTS spricht den Text 4. Nach TTS-Abschluss: followup_expected wird emittiert 5. Client wechselt in Follow-up-Modus (direktes STT ohne Wakeword) 6. Client streamt Audio bis Stille/Timeout 7. Server verarbeitet Audio → speech_recognized → NLP → ... Attributes: satellite_id: ID des Satellites session_id: Konversations-Session-ID room_id: Raum-ID timeout_seconds: Maximale Wartezeit auf Antwort followup_context: Kontext für die erwartete Antwort (Intent, Slots) """ satellite_id: str = "" session_id: str = "" room_id: str = "" timeout_seconds: float = 30.0 followup_context: dict[str, Any] = field(default_factory=dict) @dataclass class SatelliteRecordingStarted(EventData): """Event wenn server-gesteuerte Aufnahme gestartet wurde.""" satellite_id: str = "" recording_id: str = "" timeout_seconds: float = 300.0 @dataclass class SatelliteRecordingStopped(EventData): """Event wenn server-gesteuerte Aufnahme beendet wurde.""" satellite_id: str = "" recording_id: str = "" reason: str = "" duration_seconds: float = 0.0 audio_size_bytes: int = 0 filename: str = "" @dataclass class SatelliteRecordingCleared(EventData): """Event wenn der Recording-Buffer geleert wurde.""" satellite_id: str = "" recording_id: str = "" cleared_bytes: int = 0 @dataclass class SatelliteStreamStarted(EventData): """Event wenn Live-Audio-Stream gestartet wurde.""" satellite_id: str = "" target_satellite_id: str = "" stream_id: str = "" @dataclass class SatelliteStreamStopped(EventData): """Event wenn Live-Audio-Stream beendet wurde.""" satellite_id: str = "" target_satellite_id: str = "" stream_id: str = "" reason: str = "" duration_seconds: float = 0.0 bytes_forwarded: int = 0 @dataclass class SatelliteStreamRecordStarted(EventData): """Event wenn Stream-Aufnahme in Datei gestartet wurde.""" satellite_id: str = "" stream_id: str = "" filename: str = "" @dataclass class SatelliteStreamRecordStopped(EventData): """Event wenn Stream-Aufnahme beendet wurde.""" satellite_id: str = "" stream_id: str = "" filename: str = "" reason: str = "" duration_seconds: float = 0.0 file_size_bytes: int = 0