# -*- coding: utf-8 -*- """ Intent Handler Data Classes. Datenklassen für die Kommunikation zwischen NLP und Intent-Handlern. """ from dataclasses import dataclass, field from typing import Any, TYPE_CHECKING if TYPE_CHECKING: from trixy_core.conversation.session import ConversationSession @dataclass class IntentReceivedData: """ Daten die an einen Intent-Handler übergeben werden. Enthält alle Informationen die der Handler benötigt, um den Intent zu verarbeiten. Attributes: intent: Name des erkannten Intents confidence: Konfidenz der Erkennung (0.0 - 1.0) slots: Extrahierte Slot-Werte original_text: Ursprünglicher Text (STT-Ergebnis) satellite_id: ID des Satellites room_id: Raum-ID session_id: Konversations-Session-ID session: Optionale Konversations-Session response_text: Vom NLP generierte Antwort (falls vorhanden) wakeword_type: Typ des Wakewords ("custom" oder "system_command") is_authenticated: Ist der Benutzer als Admin authentifiziert? metadata: Zusätzliche Metadaten """ intent: str confidence: float = 0.0 slots: dict[str, Any] = field(default_factory=dict) original_text: str = "" satellite_id: str = "" room_id: str = "" session_id: str = "" session: "ConversationSession | None" = None response_text: str = "" wakeword_type: str = "custom" # "custom" oder "system_command" is_authenticated: bool = False # Admin-Authentifizierung metadata: dict[str, Any] = field(default_factory=dict) def get_slot(self, name: str, default: Any = None) -> Any: """ Gibt einen Slot-Wert zurueck. Bei ResolvedEntity wird der Rohwert (.raw) zurueckgegeben fuer Abwaertskompatibilitaet. Fuer den vollen ResolvedEntity get_resolved_slot() verwenden. Args: name: Slot-Name default: Standardwert Returns: Slot-Wert (String) oder default """ val = self.slots.get(name, default) # ResolvedEntity → Rohwert fuer Abwaertskompatibilitaet if val is not None and hasattr(val, "raw") and hasattr(val, "entity_type"): return val.raw return val def get_resolved_slot(self, name: str) -> Any: """ Gibt den vollen ResolvedEntity fuer einen Slot zurueck. Falls der Slot kein ResolvedEntity ist, wird None zurueckgegeben. Args: name: Slot-Name Returns: ResolvedEntity oder None """ val = self.slots.get(name) if val is not None and hasattr(val, "raw") and hasattr(val, "entity_type"): return val return None def has_slot(self, name: str) -> bool: """ Prüft ob ein Slot vorhanden ist. Args: name: Slot-Name Returns: True wenn vorhanden und nicht leer """ return name in self.slots and self.slots[name] is not None def get_required_slots(self, *names: str) -> tuple[bool, list[str]]: """ Prüft ob alle erforderlichen Slots vorhanden sind. Args: *names: Erforderliche Slot-Namen Returns: (all_present, missing_slots) """ missing = [n for n in names if not self.has_slot(n)] return len(missing) == 0, missing @dataclass class IntentResult: """ Ergebnis eines Intent-Handlers. Der Handler gibt dieses Objekt zurück, um das Ergebnis der Intent-Verarbeitung zu kommunizieren. Attributes: success: War die Verarbeitung erfolgreich? response_text: Antworttext für TTS follow_up_intent: Optionaler Folge-Intent data: Zusätzliche Ergebnisdaten error: Fehlermeldung falls nicht erfolgreich suppress_tts: TTS-Ausgabe unterdrücken """ success: bool = True response_text: str = "" follow_up_intent: str = "" follow_up_valid_responses: list[str] = field(default_factory=list) follow_up_retry_text: str = "" data: dict[str, Any] = field(default_factory=dict) error: str = "" suppress_tts: bool = False @classmethod def success_with_response(cls, response_text: str, **data: Any) -> "IntentResult": """ Erstellt ein erfolgreiches Ergebnis mit Antwort. Args: response_text: Antworttext **data: Zusätzliche Daten Returns: IntentResult """ return cls(success=True, response_text=response_text, data=data) @classmethod def failure(cls, error: str, response_text: str = "") -> "IntentResult": """ Erstellt ein Fehler-Ergebnis. Args: error: Fehlerbeschreibung response_text: Optionaler Fehlertext für TTS Returns: IntentResult """ return cls(success=False, error=error, response_text=response_text) @classmethod def silent_success(cls, **data: Any) -> "IntentResult": """ Erstellt ein erfolgreiches Ergebnis ohne TTS-Ausgabe. Args: **data: Zusätzliche Daten Returns: IntentResult """ return cls(success=True, suppress_tts=True, data=data) @classmethod def follow_up( cls, response_text: str, follow_up_intent: str, valid_responses: list[str] | None = None, retry_text: str = "", **data: Any, ) -> "IntentResult": """ Erstellt ein Ergebnis mit Rueckfrage. Args: response_text: Rueckfrage-Text (TTS) follow_up_intent: Intent der als naechstes erwartet wird valid_responses: Optionale Liste gueltiger Antworten. Wenn gesetzt, werden nur diese akzeptiert. retry_text: Text bei ungueltiger Antwort (z.B. "Bitte waehle aus: ...") **data: Zusaetzliche Daten (z.B. bisherige Bestellung) Beispiel: ```python return IntentResult.follow_up( "Welchen Belag moechtest du?", follow_up_intent="pizza_select_topping", valid_responses=["Salami", "Hawaii", "Margherita"], retry_text="Bitte waehle einen Belag: Salami, Hawaii oder Margherita", ) ``` """ return cls( success=True, response_text=response_text, follow_up_intent=follow_up_intent, follow_up_valid_responses=valid_responses or [], follow_up_retry_text=retry_text, data=data, ) def has_response(self) -> bool: """Prüft ob eine Antwort vorhanden ist.""" return bool(self.response_text) and not self.suppress_tts def needs_follow_up(self) -> bool: """Prüft ob ein Folge-Intent benötigt wird.""" return bool(self.follow_up_intent) def has_valid_responses(self) -> bool: """Prueft ob gueltige Antworten definiert sind.""" return bool(self.follow_up_valid_responses)