diff --git a/mycroft/res/text/en-us/last.voc b/mycroft/res/text/en-us/last.voc new file mode 100644 index 0000000000..1419bcd3e9 --- /dev/null +++ b/mycroft/res/text/en-us/last.voc @@ -0,0 +1,3 @@ +last choice +last option +last one \ No newline at end of file diff --git a/mycroft/skills/mycroft_skill/mycroft_skill.py b/mycroft/skills/mycroft_skill/mycroft_skill.py index 07e3bac680..fc6cb81e9d 100644 --- a/mycroft/skills/mycroft_skill/mycroft_skill.py +++ b/mycroft/skills/mycroft_skill/mycroft_skill.py @@ -42,6 +42,8 @@ from mycroft.util import ( camel_case_split ) from mycroft.util.log import LOG +from mycroft.util.format import pronounce_number, join_list +from mycroft.util.parse import match_one, extract_number from .event_container import EventContainer, create_wrapper, get_handler_name from ..event_scheduler import EventSchedulerInterface @@ -368,9 +370,6 @@ class MycroftSkill: """ data = data or {} - if not self.dialog_renderer.render(dialog, data): - raise ValueError('dialog message required') - def on_fail_default(utterance): fail_data = data.copy() fail_data['utterance'] = utterance @@ -390,8 +389,11 @@ class MycroftSkill: validator = validator or validator_default # Speak query and wait for user response - self.speak(self.dialog_renderer.render(dialog, data), - expect_response=True, wait=True) + utterance = self.dialog_renderer.render(dialog, data) + if utterance: + self.speak(utterance, expect_response=True, wait=True) + else: + self.bus.emit(Message('mycroft.mic.listen')) return self._wait_response(is_cancel, validator, on_fail_fn, num_retries) @@ -427,7 +429,10 @@ class MycroftSkill: return None line = on_fail(response) - self.speak(line, expect_response=True) + if line: + self.speak(line, expect_response=True) + else: + self.bus.emit(Message('mycroft.mic.listen')) def ask_yesno(self, prompt, data=None): """Read prompt and wait for a yes/no answer @@ -451,6 +456,57 @@ class MycroftSkill: else: return resp + def ask_selection(self, options, dialog='', data=None, min_conf=0.65, numeric=False): + """ Read options, ask dialog question and wait for an answer + + This automatically deals with fuzzy matching and selection by number + e.g. + "first option" + "last option" + "second option" + "option number four" + + Args: + options (list): list of options to present user + dialog (str): a dialog id or string to read after presenting options + data (dict): Data used to render the dialog + min_conf (float): min confidence for fuzzy matching, None will be returned bellow this threshold + numeric (bool): speak options as a numeric menu + Returns: + string: list element selected by user, or None + """ + assert isinstance(options, list) + + if not len(options): + return None + elif len(options) == 1: + return options[0] + + if numeric: + for idx, opt in enumerate(options): + opt_str = "{number}, {option_text}".format(number=pronounce_number(idx + 1, self.lang), + option_text=opt) + self.speak(opt_str, wait=True) + else: + opt_str = join_list(options, "or", lang=self.lang) + "?" + self.speak(opt_str, wait=True) + + resp = self.get_response(dialog=dialog, data=data) + + if resp: + match, score = match_one(resp, options) + if score < min_conf: + if self.voc_match(resp, 'last'): + resp = options[-1] + else: + num = extract_number(resp, self.lang, ordinals=True) + resp = None + if num and num < len(options): + resp = options[num - 1] + else: + resp = match + return resp + def voc_match(self, utt, voc_filename, lang=None): """Determine if the given utterance contains the vocabulary provided.