forked from platypush/platypush
Refactored AssistantEvent
.
`AssistantEvent.assistant` is now modelled as an opaque object that behaves the following way: - The underlying plugin name is saved under `event.args['_assistant']`. - `event.assistant` is a property that returns the assistant instance via `get_plugin`. - `event.assistant` is reported as a string (plugin qualified name) upon event dump. This allows event hooks to easily use `event.assistant` to interact with the underlying assistant and easily modify the conversation flow, while event hook conditions can still be easily modelled as equality operations between strings.
This commit is contained in:
parent
a670f01647
commit
8378bee7c6
4 changed files with 108 additions and 45 deletions
|
@ -257,26 +257,29 @@ class Event(Message):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def __str__(self):
|
def as_dict(self):
|
||||||
"""
|
"""
|
||||||
Overrides the str() operator and converts
|
Converts the event into a dictionary
|
||||||
the message into a UTF-8 JSON string
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
args = copy.deepcopy(self.args)
|
args = copy.deepcopy(self.args)
|
||||||
flatten(args)
|
flatten(args)
|
||||||
|
return {
|
||||||
return json.dumps(
|
|
||||||
{
|
|
||||||
'type': 'event',
|
'type': 'event',
|
||||||
'target': self.target,
|
'target': self.target,
|
||||||
'origin': self.origin if hasattr(self, 'origin') else None,
|
'origin': self.origin if hasattr(self, 'origin') else None,
|
||||||
'id': self.id if hasattr(self, 'id') else None,
|
'id': self.id if hasattr(self, 'id') else None,
|
||||||
'_timestamp': self.timestamp,
|
'_timestamp': self.timestamp,
|
||||||
'args': {'type': self.type, **args},
|
'args': {'type': self.type, **args},
|
||||||
},
|
}
|
||||||
cls=self.Encoder,
|
|
||||||
)
|
def __str__(self):
|
||||||
|
"""
|
||||||
|
Overrides the str() operator and converts
|
||||||
|
the message into a UTF-8 JSON string
|
||||||
|
"""
|
||||||
|
args = copy.deepcopy(self.args)
|
||||||
|
flatten(args)
|
||||||
|
return json.dumps(self.as_dict(), cls=self.Encoder)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
|
@ -1,27 +1,53 @@
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from typing import Optional
|
from typing import Optional, Union
|
||||||
|
|
||||||
from platypush.context import get_plugin
|
from platypush.context import get_plugin
|
||||||
from platypush.message.event import Event
|
from platypush.message.event import Event
|
||||||
|
from platypush.plugins.assistant import AssistantPlugin
|
||||||
|
from platypush.utils import get_plugin_name_by_class
|
||||||
|
|
||||||
|
|
||||||
class AssistantEvent(Event):
|
class AssistantEvent(Event):
|
||||||
"""Base class for assistant events"""
|
"""Base class for assistant events"""
|
||||||
|
|
||||||
def __init__(self, *args, assistant: Optional[str] = None, **kwargs):
|
def __init__(
|
||||||
|
self, *args, assistant: Optional[Union[str, AssistantPlugin]] = None, **kwargs
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
:param assistant: Name of the assistant plugin that triggered the event.
|
:param assistant: Name of the assistant plugin that triggered the event.
|
||||||
"""
|
"""
|
||||||
super().__init__(*args, assistant=assistant, **kwargs)
|
assistant = assistant or kwargs.get('assistant')
|
||||||
|
if assistant:
|
||||||
|
assistant = (
|
||||||
|
assistant
|
||||||
|
if isinstance(assistant, str)
|
||||||
|
else get_plugin_name_by_class(assistant.__class__)
|
||||||
|
)
|
||||||
|
|
||||||
|
kwargs['_assistant'] = assistant
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _assistant(self):
|
def assistant(self) -> Optional[AssistantPlugin]:
|
||||||
return (
|
assistant = self.args.get('_assistant')
|
||||||
get_plugin(self.args.get('assistant'))
|
if not assistant:
|
||||||
if self.args.get('assistant')
|
return None
|
||||||
else None
|
|
||||||
)
|
return get_plugin(assistant)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
evt_dict = super().as_dict()
|
||||||
|
evt_args = {**evt_dict['args']}
|
||||||
|
assistant = evt_args.pop('_assistant', None)
|
||||||
|
if assistant:
|
||||||
|
evt_args['assistant'] = assistant
|
||||||
|
|
||||||
|
return {
|
||||||
|
**evt_dict,
|
||||||
|
'args': evt_args,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ConversationStartEvent(AssistantEvent):
|
class ConversationStartEvent(AssistantEvent):
|
||||||
|
@ -95,8 +121,8 @@ class SpeechRecognizedEvent(AssistantEvent):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = super().matches_condition(condition)
|
result = super().matches_condition(condition)
|
||||||
if result.is_match and self._assistant and 'phrase' in condition.args:
|
if result.is_match and self.assistant and 'phrase' in condition.args:
|
||||||
self._assistant.stop_conversation()
|
self.assistant.stop_conversation()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
|
@ -122,6 +122,12 @@ class Plugin(EventGenerator, ExtensionWithManifest): # lgtm [py/missing-call-to
|
||||||
assert entities, 'entities plugin not initialized'
|
assert entities, 'entities plugin not initialized'
|
||||||
return entities
|
return entities
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""
|
||||||
|
:return: The qualified name of the plugin.
|
||||||
|
"""
|
||||||
|
return get_plugin_name_by_class(self.__class__)
|
||||||
|
|
||||||
def run(self, method, *args, **kwargs):
|
def run(self, method, *args, **kwargs):
|
||||||
assert (
|
assert (
|
||||||
method in self.registered_actions
|
method in self.registered_actions
|
||||||
|
|
|
@ -8,24 +8,7 @@ from typing import Any, Collection, Dict, Optional, Type
|
||||||
from platypush.context import get_bus, get_plugin
|
from platypush.context import get_bus, get_plugin
|
||||||
from platypush.entities.assistants import Assistant
|
from platypush.entities.assistants import Assistant
|
||||||
from platypush.entities.managers.assistants import AssistantEntityManager
|
from platypush.entities.managers.assistants import AssistantEntityManager
|
||||||
from platypush.message.event.assistant import (
|
from platypush.message.event import Event as AppEvent
|
||||||
AlarmEndEvent,
|
|
||||||
AlarmStartedEvent,
|
|
||||||
AlertEndEvent,
|
|
||||||
AlertStartedEvent,
|
|
||||||
AssistantEvent,
|
|
||||||
ConversationEndEvent,
|
|
||||||
ConversationStartEvent,
|
|
||||||
ConversationTimeoutEvent,
|
|
||||||
HotwordDetectedEvent,
|
|
||||||
MicMutedEvent,
|
|
||||||
MicUnmutedEvent,
|
|
||||||
NoResponseEvent,
|
|
||||||
ResponseEvent,
|
|
||||||
SpeechRecognizedEvent,
|
|
||||||
TimerEndEvent,
|
|
||||||
TimerStartedEvent,
|
|
||||||
)
|
|
||||||
from platypush.plugins import Plugin, action
|
from platypush.plugins import Plugin, action
|
||||||
from platypush.utils import get_plugin_name_by_class
|
from platypush.utils import get_plugin_name_by_class
|
||||||
|
|
||||||
|
@ -182,6 +165,17 @@ class AssistantPlugin(Plugin, AssistantEntityManager, ABC):
|
||||||
self.publish_entities([self])
|
self.publish_entities([self])
|
||||||
return asdict(self._state)
|
return asdict(self._state)
|
||||||
|
|
||||||
|
@action
|
||||||
|
def render_response(self, text: str, *_, **__):
|
||||||
|
"""
|
||||||
|
Render a response text as audio over the configured TTS plugin.
|
||||||
|
|
||||||
|
:param text: Text to render.
|
||||||
|
"""
|
||||||
|
self._on_response_render_start(text)
|
||||||
|
self._render_response(text)
|
||||||
|
self._on_response_render_end()
|
||||||
|
|
||||||
def _get_tts_plugin(self):
|
def _get_tts_plugin(self):
|
||||||
if not self.tts_plugin:
|
if not self.tts_plugin:
|
||||||
return None
|
return None
|
||||||
|
@ -201,11 +195,13 @@ class AssistantPlugin(Plugin, AssistantEntityManager, ABC):
|
||||||
|
|
||||||
audio.play(self._conversation_start_sound)
|
audio.play(self._conversation_start_sound)
|
||||||
|
|
||||||
def _send_event(self, event_type: Type[AssistantEvent], **kwargs):
|
def _send_event(self, event_type: Type[AppEvent], **kwargs):
|
||||||
self.publish_entities([self])
|
self.publish_entities([self])
|
||||||
get_bus().post(event_type(assistant=self._plugin_name, **kwargs))
|
get_bus().post(event_type(assistant=self._plugin_name, **kwargs))
|
||||||
|
|
||||||
def _on_conversation_start(self):
|
def _on_conversation_start(self):
|
||||||
|
from platypush.message.event.assistant import ConversationStartEvent
|
||||||
|
|
||||||
self._last_response = None
|
self._last_response = None
|
||||||
self._last_query = None
|
self._last_query = None
|
||||||
self._conversation_running.set()
|
self._conversation_running.set()
|
||||||
|
@ -213,66 +209,98 @@ class AssistantPlugin(Plugin, AssistantEntityManager, ABC):
|
||||||
self._play_conversation_start_sound()
|
self._play_conversation_start_sound()
|
||||||
|
|
||||||
def _on_conversation_end(self):
|
def _on_conversation_end(self):
|
||||||
|
from platypush.message.event.assistant import ConversationEndEvent
|
||||||
|
|
||||||
self._conversation_running.clear()
|
self._conversation_running.clear()
|
||||||
self._send_event(ConversationEndEvent)
|
self._send_event(ConversationEndEvent)
|
||||||
|
|
||||||
def _on_conversation_timeout(self):
|
def _on_conversation_timeout(self):
|
||||||
|
from platypush.message.event.assistant import ConversationTimeoutEvent
|
||||||
|
|
||||||
self._last_response = None
|
self._last_response = None
|
||||||
self._last_query = None
|
self._last_query = None
|
||||||
self._conversation_running.clear()
|
self._conversation_running.clear()
|
||||||
self._send_event(ConversationTimeoutEvent)
|
self._send_event(ConversationTimeoutEvent)
|
||||||
|
|
||||||
def _on_no_response(self):
|
def _on_no_response(self):
|
||||||
|
from platypush.message.event.assistant import NoResponseEvent
|
||||||
|
|
||||||
self._last_response = None
|
self._last_response = None
|
||||||
self._conversation_running.clear()
|
self._conversation_running.clear()
|
||||||
self._send_event(NoResponseEvent)
|
self._send_event(NoResponseEvent)
|
||||||
|
|
||||||
def _on_reponse_rendered(self, text: Optional[str]):
|
def _on_response_render_start(self, text: Optional[str]):
|
||||||
|
from platypush.message.event.assistant import ResponseEvent
|
||||||
|
|
||||||
self._last_response = text
|
self._last_response = text
|
||||||
self._send_event(ResponseEvent, response_text=text)
|
self._send_event(ResponseEvent, response_text=text)
|
||||||
tts = self._get_tts_plugin()
|
|
||||||
|
|
||||||
|
def _render_response(self, text: Optional[str]):
|
||||||
|
tts = self._get_tts_plugin()
|
||||||
if tts and text:
|
if tts and text:
|
||||||
self.stop_conversation()
|
self.stop_conversation()
|
||||||
tts.say(text=text, **self.tts_plugin_args)
|
tts.say(text=text, **self.tts_plugin_args)
|
||||||
|
|
||||||
|
def _on_response_render_end(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def _on_hotword_detected(self, hotword: Optional[str]):
|
def _on_hotword_detected(self, hotword: Optional[str]):
|
||||||
|
from platypush.message.event.assistant import HotwordDetectedEvent
|
||||||
|
|
||||||
self._send_event(HotwordDetectedEvent, hotword=hotword)
|
self._send_event(HotwordDetectedEvent, hotword=hotword)
|
||||||
|
|
||||||
def _on_speech_recognized(self, phrase: Optional[str]):
|
def _on_speech_recognized(self, phrase: Optional[str]):
|
||||||
|
from platypush.message.event.assistant import SpeechRecognizedEvent
|
||||||
|
|
||||||
phrase = (phrase or '').lower().strip()
|
phrase = (phrase or '').lower().strip()
|
||||||
self._last_query = phrase
|
self._last_query = phrase
|
||||||
self._send_event(SpeechRecognizedEvent, phrase=phrase)
|
self._send_event(SpeechRecognizedEvent, phrase=phrase)
|
||||||
|
|
||||||
def _on_alarm_start(self):
|
def _on_alarm_start(self):
|
||||||
|
from platypush.message.event.assistant import AlarmStartedEvent
|
||||||
|
|
||||||
self._cur_alert_type = AlertType.ALARM
|
self._cur_alert_type = AlertType.ALARM
|
||||||
self._send_event(AlarmStartedEvent)
|
self._send_event(AlarmStartedEvent)
|
||||||
|
|
||||||
def _on_alarm_end(self):
|
def _on_alarm_end(self):
|
||||||
|
from platypush.message.event.assistant import AlarmEndEvent
|
||||||
|
|
||||||
self._cur_alert_type = None
|
self._cur_alert_type = None
|
||||||
self._send_event(AlarmEndEvent)
|
self._send_event(AlarmEndEvent)
|
||||||
|
|
||||||
def _on_timer_start(self):
|
def _on_timer_start(self):
|
||||||
|
from platypush.message.event.assistant import TimerStartedEvent
|
||||||
|
|
||||||
self._cur_alert_type = AlertType.TIMER
|
self._cur_alert_type = AlertType.TIMER
|
||||||
self._send_event(TimerStartedEvent)
|
self._send_event(TimerStartedEvent)
|
||||||
|
|
||||||
def _on_timer_end(self):
|
def _on_timer_end(self):
|
||||||
|
from platypush.message.event.assistant import TimerEndEvent
|
||||||
|
|
||||||
self._cur_alert_type = None
|
self._cur_alert_type = None
|
||||||
self._send_event(TimerEndEvent)
|
self._send_event(TimerEndEvent)
|
||||||
|
|
||||||
def _on_alert_start(self):
|
def _on_alert_start(self):
|
||||||
|
from platypush.message.event.assistant import AlertStartedEvent
|
||||||
|
|
||||||
self._cur_alert_type = AlertType.ALERT
|
self._cur_alert_type = AlertType.ALERT
|
||||||
self._send_event(AlertStartedEvent)
|
self._send_event(AlertStartedEvent)
|
||||||
|
|
||||||
def _on_alert_end(self):
|
def _on_alert_end(self):
|
||||||
|
from platypush.message.event.assistant import AlertEndEvent
|
||||||
|
|
||||||
self._cur_alert_type = None
|
self._cur_alert_type = None
|
||||||
self._send_event(AlertEndEvent)
|
self._send_event(AlertEndEvent)
|
||||||
|
|
||||||
def _on_mute(self):
|
def _on_mute(self):
|
||||||
|
from platypush.message.event.assistant import MicMutedEvent
|
||||||
|
|
||||||
self._is_muted = True
|
self._is_muted = True
|
||||||
self._send_event(MicMutedEvent)
|
self._send_event(MicMutedEvent)
|
||||||
|
|
||||||
def _on_unmute(self):
|
def _on_unmute(self):
|
||||||
|
from platypush.message.event.assistant import MicUnmutedEvent
|
||||||
|
|
||||||
self._is_muted = False
|
self._is_muted = False
|
||||||
self._send_event(MicUnmutedEvent)
|
self._send_event(MicUnmutedEvent)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue