diff --git a/homeassistant/components/conversation/__init__.py b/homeassistant/components/conversation/__init__.py index a82034a4237..b2648d3d1f6 100644 --- a/homeassistant/components/conversation/__init__.py +++ b/homeassistant/components/conversation/__init__.py @@ -61,7 +61,6 @@ async def get_agent(hass: core.HomeAssistant) -> AbstractConversationAgent: async def async_setup(hass, config): """Register the process service.""" - hass.data[DATA_CONFIG] = config async def handle_service(service): @@ -77,6 +76,7 @@ async def async_setup(hass, config): DOMAIN, SERVICE_PROCESS, handle_service, schema=SERVICE_PROCESS_SCHEMA ) hass.http.register_view(ConversationProcessView()) + hass.http.register_view(ConversationHandleView()) hass.components.websocket_api.async_register_command(websocket_process) hass.components.websocket_api.async_register_command(websocket_get_agent_info) hass.components.websocket_api.async_register_command(websocket_set_onboarding) @@ -162,3 +162,40 @@ class ConversationProcessView(http.HomeAssistantView): ) return self.json(intent_result) + + +class ConversationHandleView(http.HomeAssistantView): + """View to handle intents from JSON.""" + + url = "/api/conversation/handle" + name = "api:conversation:handle" + + @RequestDataValidator( + vol.Schema( + { + vol.Required("name"): cv.string, + vol.Optional("data"): vol.Schema({cv.string: object}), + } + ) + ) + async def post(self, request, data): + """Handle intent with name/data.""" + hass = request.app["hass"] + + try: + intent_name = data["name"] + slots = { + key: {"value": value} for key, value in data.get("data", {}).items() + } + intent_result = await intent.async_handle( + hass, DOMAIN, intent_name, slots, "" + ) + except intent.IntentHandleError as err: + intent_result = intent.IntentResponse() + intent_result.async_set_speech(str(err)) + + if intent_result is None: + intent_result = intent.IntentResponse() + intent_result.async_set_speech("Sorry, I couldn't handle that") + + return self.json(intent_result) diff --git a/tests/components/conversation/test_init.py b/tests/components/conversation/test_init.py index ff44eaccc8e..fc6508159ea 100644 --- a/tests/components/conversation/test_init.py +++ b/tests/components/conversation/test_init.py @@ -124,6 +124,51 @@ async def test_http_processing_intent(hass, hass_client): } +async def test_http_handle_intent(hass, hass_client): + """Test handle intent via HTTP API.""" + + class TestIntentHandler(intent.IntentHandler): + """Test Intent Handler.""" + + intent_type = "OrderBeer" + + async def async_handle(self, intent): + """Handle the intent.""" + response = intent.create_response() + response.async_set_speech( + "I've ordered a {}!".format(intent.slots["type"]["value"]) + ) + response.async_set_card( + "Beer ordered", "You chose a {}.".format(intent.slots["type"]["value"]) + ) + return response + + intent.async_register(hass, TestIntentHandler()) + + result = await async_setup_component( + hass, + "conversation", + {"conversation": {"intents": {"OrderBeer": ["I would like the {type} beer"]}}}, + ) + assert result + + client = await hass_client() + resp = await client.post( + "/api/conversation/handle", + json={"name": "OrderBeer", "data": {"type": "Belgian"}}, + ) + + assert resp.status == 200 + data = await resp.json() + + assert data == { + "card": { + "simple": {"content": "You chose a Belgian.", "title": "Beer ordered"} + }, + "speech": {"plain": {"extra_data": None, "speech": "I've ordered a Belgian!"}}, + } + + @pytest.mark.parametrize("sentence", ("turn on kitchen", "turn kitchen on")) async def test_turn_on_intent(hass, sentence): """Test calling the turn on intent."""