Repository: RasaHQ/rasa
Pull request: Use questionary buttons when interacting with assistant via the terminal
**Proposed changes**:
- if the bot message contains buttons, the user will be able to chose a reply from a list choice instead of having to type the buttons payload 
- Fixes #3020 
- Works in both interactive learning mode and with `rasa shell`


**Status (please check what you already did)**:
- [x] made PR ready for code review
- [ ] added some tests for the functionality
- [ ] updated the documentation
- [x] updated the changelog
- [x] reformat files using `black` (please check [Readme](https://github.com/RasaHQ/rasa_nlu#code-style) for instructions)


Problem statement:
Allow users to select buttons when running from cmdline
When an utterance has buttons, we list them like:
```
1: ...
2: ...
```
Would be nice to allow users to select these by, for instance, typing "1" or "2".  Or using questionary

Hints from issue discussion:

Commit message: use old method for printing in restore mode, update changelog

Patch to review:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index c1191ba2817e..5b0ba322d364 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,6 +6,30 @@ Rasa Change Log
 All notable changes to this project will be documented in this file.
 This project adheres to `Semantic Versioning`_ starting with version 1.0.
 
+[Unreleased 1.0.2.aX] - `master`_
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Added
+-----
+
+Changed
+-------
+
+Removed
+-------
+
+Fixed
+-----
+- buttons can now be properly selected during cmdline chat or when in interactive learning
+
+
+[1.0.1] - 2019-05-21
+^^^^^^^^^^^^^^^^^^^^
+
+Fixed
+-----
+- updated installation command in docs for Rasa X
+
 [1.0.0] - 2019-05-21
 ^^^^^^^^^^^^^^^^^^^^
 
diff --git a/rasa/core/channels/console.py b/rasa/core/channels/console.py
index d1c8a256bbb2..8602b985c69d 100644
--- a/rasa/core/channels/console.py
+++ b/rasa/core/channels/console.py
@@ -17,7 +17,7 @@
 
 
 def print_bot_output(message, color=rasa.cli.utils.bcolors.OKBLUE):
-    if "text" in message:
+    if ("text" in message) and not ("buttons" in message):
         rasa.cli.utils.print_color(message.get("text"), color=color)
 
     if "image" in message:
@@ -29,9 +29,17 @@ def print_bot_output(message, color=rasa.cli.utils.bcolors.OKBLUE):
         )
 
     if "buttons" in message:
-        rasa.cli.utils.print_color("Buttons:", color=color)
-        for idx, button in enumerate(message.get("buttons")):
-            rasa.cli.utils.print_color(button_to_string(button, idx), color=color)
+        choices = [
+            button_to_string(button, idx)
+            for idx, button in enumerate(message.get("buttons"))
+        ]
+
+        question = questionary.select(
+            message.get("text"),
+            choices,
+            style=Style([("qmark", "#6d91d3"), ("", "#6d91d3"), ("answer", "#b373d6")]),
+        )
+        return question
 
     if "elements" in message:
         rasa.cli.utils.print_color("Elements:", color=color)
@@ -50,14 +58,20 @@ def print_bot_output(message, color=rasa.cli.utils.bcolors.OKBLUE):
         )
 
 
-def get_cmd_input():
-    response = questionary.text(
-        "", qmark="Your input ->", style=Style([("qmark", "#b373d6"), ("", "#b373d6")])
-    ).ask()
+def get_cmd_input(button_question: questionary.Question):
+    if button_question:
+        text = button_question.ask()
+        response = text[text.find("(") + 1 : text.find(")")]  # get intent
+
+    else:
+        response = questionary.text(
+            "",
+            qmark="Your input ->",
+            style=Style([("qmark", "#b373d6"), ("", "#b373d6")]),
+        ).ask()
+
     if response is not None:
         return response.strip()
-    else:
-        return None
 
 
 async def send_message_receive_block(server_url, auth_token, sender_id, message):
@@ -103,8 +117,10 @@ async def record_messages(
     )
 
     num_messages = 0
+    button_question = False
     while not utils.is_limit_reached(num_messages, max_message_limit):
-        text = get_cmd_input()
+        text = get_cmd_input(button_question)
+
         if text == exit_text or text is None:
             break
 
@@ -113,13 +129,13 @@ async def record_messages(
                 server_url, auth_token, sender_id, text
             )
             async for response in bot_responses:
-                print_bot_output(response)
+                button_question = print_bot_output(response)
         else:
             bot_responses = await send_message_receive_block(
                 server_url, auth_token, sender_id, text
             )
             for response in bot_responses:
-                print_bot_output(response)
+                button_question = print_bot_output(response)
 
         num_messages += 1
     return num_messages
diff --git a/rasa/core/restore.py b/rasa/core/restore.py
index 3db57cffaa60..1a3f87d9ae86 100644
--- a/rasa/core/restore.py
+++ b/rasa/core/restore.py
@@ -94,8 +94,17 @@ async def replay_events(tracker: DialogueStateTracker, agent: "Agent") -> None:
                 event.text, sender_id=tracker.sender_id, output_channel=out
             )
             for m in out.messages:
+                buttons = m.pop("buttons", None)  # for non-terminal stdin
                 console.print_bot_output(m)
 
+                if buttons:
+                    color = rasa.cli.utils.bcolors.OKBLUE
+                    rasa.cli.utils.print_color("Buttons:", color=color)
+                    for idx, button in enumerate(buttons):
+                        rasa.cli.utils.print_color(
+                            console.button_to_string(button, idx), color=color
+                        )
+
             tracker = agent.tracker_store.retrieve(tracker.sender_id)
             last_prediction = actions_since_last_utterance(tracker)
 
diff --git a/rasa/core/training/interactive.py b/rasa/core/training/interactive.py
index 757758d73b39..1db5f426c334 100644
--- a/rasa/core/training/interactive.py
+++ b/rasa/core/training/interactive.py
@@ -895,6 +895,26 @@ async def _predict_till_next_listen(
 
         await _plot_trackers(sender_ids, plot_file, endpoint)
 
+    tracker_dump = await retrieve_tracker(
+        endpoint, sender_id, EventVerbosity.AFTER_RESTART
+    )
+    events = tracker_dump.get("events", [])
+    last_event = events[-2]  # last event before action_listen
+    # if bot message includes buttons the user will get a list choice to reply
+    # the list choice is displayed in place of action listen
+    if last_event.get("event") == BotUttered.type_name and last_event["data"].get(
+        "buttons", None
+    ):
+        data = last_event["data"]
+        message = last_event.get("text", "")
+        choices = [
+            button_to_string(button, idx)
+            for idx, button in enumerate(data.get("buttons"))
+        ]
+        answer = questionary.select(message, choices).ask()
+        button_payload = answer[answer.find("(") + 1 : answer.find(")")]
+        await send_message(endpoint, sender_id, button_payload)
+
 
 async def _correct_wrong_nlu(
     corrected_nlu: Dict[Text, Any],


Code review comments:
diff_hunk: @@ -50,14 +58,20 @@ def print_bot_output(message, color=rasa.cli.utils.bcolors.OKBLUE):
         )
 
 
-def get_cmd_input():
-    response = questionary.text(
-        "", qmark="Your input ->", style=Style([("qmark", "#b373d6"), ("", "#b373d6")])
-    ).ask()
+def get_cmd_input(button_question: questionary.Question):
+    if button_question:
path: rasa/core/channels/console.py
text: @user1:
Can we make it more expressive? I would initialize `button_question` with `None` and also return `None` in `print_bot_output` at the end. The check here I would modify to `button_question is not None`.

diff_hunk: @@ -17,7 +17,7 @@
 
 
 def print_bot_output(message, color=rasa.cli.utils.bcolors.OKBLUE):
path: rasa/core/channels/console.py
text: @user1:
Add return type

diff_hunk: @@ -50,14 +58,20 @@ def print_bot_output(message, color=rasa.cli.utils.bcolors.OKBLUE):
         )
 
 
-def get_cmd_input():
-    response = questionary.text(
-        "", qmark="Your input ->", style=Style([("qmark", "#b373d6"), ("", "#b373d6")])
-    ).ask()
+def get_cmd_input(button_question: questionary.Question):
+    if button_question:
+        text = button_question.ask()
+        response = text[text.find("(") + 1 : text.find(")")]  # get intent
path: rasa/core/channels/console.py
text: @user1:
Can we extract that into a separate method as the exact code is also used in interactive learning?

diff_hunk: @@ -50,14 +58,20 @@ def print_bot_output(message, color=rasa.cli.utils.bcolors.OKBLUE):
         )
 
 
-def get_cmd_input():
-    response = questionary.text(
-        "", qmark="Your input ->", style=Style([("qmark", "#b373d6"), ("", "#b373d6")])
-    ).ask()
+def get_cmd_input(button_question: questionary.Question):
path: rasa/core/channels/console.py
text: @user1:
Add return type