Skip to content

voice_commands

voice_commands

Parse spoken commands into desktop tool actions.

Functions:

is_explicit_voice_command

is_explicit_voice_command(text: str) -> bool

True only when the user actually uttered an action verb.

A bare app name may be a valid follow-up, but it may also be Whisper's favourite silence hallucination. The realtime fast path therefore acts only on explicit imperatives such as « ouvre Notes ».

Source code in src/diapason/desktop/voice_commands.py
def is_explicit_voice_command(text: str) -> bool:
    """True only when the user actually uttered an action verb.

    A bare app name may be a valid follow-up, but it may also be Whisper's
    favourite silence hallucination. The realtime fast path therefore acts
    only on explicit imperatives such as « ouvre Notes ».
    """
    raw = (text or "").strip()
    if raw.lower().strip(".!?") in _APP_ALIASES:
        return False
    return bool(_OPEN_RE.match(raw) or _BROWSE_RE.match(raw) or _SEARCH_RE.match(raw))

finalize_dictation

finalize_dictation(
    raw: str,
    *,
    polish: bool = True,
    llm_polish: bool | None = None,
    email_mode: bool | None = None,
    use_dictionary: bool | None = None,
) -> dict[str, Any]

Decide command vs paste; polish only on the paste path.

Command detection runs on raw STT first, then on locally filler-stripped text (no dictionary / LLM) so « um open Spotify » still routes as a command.

Source code in src/diapason/desktop/voice_commands.py
def finalize_dictation(
    raw: str,
    *,
    polish: bool = True,
    llm_polish: bool | None = None,
    email_mode: bool | None = None,
    use_dictionary: bool | None = None,
) -> dict[str, Any]:
    """Decide command vs paste; polish only on the paste path.

    Command detection runs on raw STT first, then on locally filler-stripped
    text (no dictionary / LLM) so « um open Spotify » still routes as a command.
    """
    from diapason.speech.dictate_polish import polish_dictation, polish_pipeline

    cfg_dict = True
    cfg_llm = False
    cfg_email = False
    cfg_timeout = 2000
    cfg_auto_learn = True
    dict_path = ""
    do_polish = bool(polish)
    try:
        from diapason.core.config import load_config

        d = load_config().dictation
        # Request polish=False always wins; polish=True also needs config on
        do_polish = bool(polish) and bool(d.polish)
        cfg_dict = (
            bool(d.dictionary) if use_dictionary is None else bool(use_dictionary)
        )
        cfg_llm = bool(d.llm_polish) if llm_polish is None else bool(llm_polish)
        mode = (d.email_mode or "off").strip().lower()
        if email_mode is None:
            if mode in ("on", "true", "1", "yes"):
                cfg_email = True
            elif mode == "auto":
                try:
                    from diapason.desktop.frontmost import is_email_composer_context

                    cfg_email = is_email_composer_context()
                except Exception:
                    cfg_email = False
            else:
                cfg_email = False
        else:
            cfg_email = bool(email_mode)
        cfg_timeout = int(d.llm_timeout_ms or 2000)
        dict_path = (d.dictionary_path or "").strip()
        cfg_auto_learn = bool(getattr(d, "auto_learn", True))
    except Exception:
        do_polish = bool(polish)
        if use_dictionary is not None:
            cfg_dict = bool(use_dictionary)
        if llm_polish is not None:
            cfg_llm = bool(llm_polish)
        if email_mode is not None:
            cfg_email = bool(email_mode)

    if not polish:
        do_polish = False

    text_gate = True
    try:
        from diapason.core.config import load_config as _lc

        text_gate = bool(_lc().speech.wakeword.text_gate)
    except Exception:
        text_gate = True

    from diapason.speech.wake_phrases import has_wake_word, strip_wake_word

    wake_hit = bool(text_gate and has_wake_word(raw))
    stripped = strip_wake_word(raw) if wake_hit else (raw or "").strip()

    action = parse_voice_command(raw)
    if action.kind == "none" and wake_hit and stripped:
        action = parse_voice_command(stripped)
    if action.kind == "none":
        cleaned_for_cmd = polish_dictation(
            stripped if wake_hit else raw, aggressive=True
        )
        if cleaned_for_cmd and cleaned_for_cmd != (raw or "").strip():
            action = parse_voice_command(cleaned_for_cmd)

    if action.kind != "none":
        exec_result = execute_voice_action(action)
        return {
            "mode": "command",
            "original": raw,
            "text": stripped if wake_hit and stripped else raw,
            "action": exec_result,
            "meta": {"wake_word": wake_hit},
        }

    # Bare wake (« Hey Diapason ») → hint UI to open Talk
    if wake_hit and not stripped:
        return {
            "mode": "wake",
            "original": raw,
            "text": "",
            "action": None,
            "meta": {"wake_word": True, "suggest": "talk_open"},
        }

    paste_source = stripped if wake_hit and stripped else raw
    text = polish_pipeline(
        paste_source,
        polish=do_polish,
        use_dictionary=cfg_dict and do_polish,
        llm_polish=cfg_llm and do_polish,
        email_mode=cfg_email,
        llm_timeout_ms=cfg_timeout,
        dictionary_path=dict_path or None,
    )

    learned = 0
    if (
        cfg_auto_learn
        and do_polish
        and cfg_dict
        and (paste_source or "").strip()
        and text.strip()
        and text.strip() != (paste_source or "").strip()
    ):
        try:
            from diapason.speech.dictation_dictionary import learn_from_correction

            entries = learn_from_correction(paste_source, text, path=dict_path or None)
            learned = len(entries)
        except Exception:
            learned = 0

    return {
        "mode": "paste",
        "original": raw,
        "text": text,
        "action": None,
        "meta": {
            "dictionary": cfg_dict and do_polish,
            "llm_polish": cfg_llm and do_polish,
            "email_mode": cfg_email,
            "wake_word": wake_hit,
            "auto_learn": cfg_auto_learn,
            "learned": learned,
        },
    }