fix(settings): make model downloads explicit and cancel the whole intent (#1006)

* fix(settings): make model downloads explicit and cancel the whole intent

"Use it" on the recommendation card applied a different model than the one
shown, a cancelled or failed apply left the staged model behind so the next
Test Dictation downloaded it, and cancelling a download tore down and then
reloaded the previous model while the dialog waited.

- Recommendation is now passive text, compared by size tier, with a link to
  docs/MODEL_SELECTION.md. The "Use it" button is gone.
- Every Settings download asks first and shows size and source host.
  Declining, cancelling or failing drops the staged model spec and puts the
  pickers back on the saved config.
- Test Dictation refuses a model that is not on disk instead of applying it.
- reconfigure() fetches a missing model before releasing the loaded one, so
  a cancel restores state without reloading or rehashing the old model.
  Cancellation state is reset only when a download claim is taken or ended.
- Follow-layout mode shows the resolved language in the simple Main language
  control, read-only, and simple edits keep the "layout" setting.

Fixes #979
Fixes #980
Fixes #981
Fixes #982
Fixes #985
Fixes #988

* fix(settings): restore follow-layout mode when a model change is declined

* fix(settings): keep follow-layout and dictation consistent during model changes

* style(tests): format follow-layout regression tests

* fix(recognition): skip resume reinit while a model download is running

* fix(recognition): keep a lazy reload pending when resume is skipped during a download

---------

Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
This commit is contained in:
devin-ai-integration[bot]
2026-10-21 01:04:10 -07:00
committed by GitHub
co-authored by Jatin K Malik
parent f013b7ff88
commit 3e4230ffc2
14 changed files with 1143 additions and 319 deletions
+62
View File
@@ -2,6 +2,7 @@
import importlib
import sys
from typing import Any
from unittest.mock import MagicMock, Mock, patch
import pytest
@@ -387,6 +388,67 @@ def test_auto_detect_shows_up_as_the_other_languages_switch(settings_dialog, dia
dialog.simple_multi_switch.set_active.assert_called_once_with(True)
def test_follow_layout_shows_and_disables_the_resolved_simple_language(
settings_dialog: Any, dialog_class: Any
) -> None:
dialog = _dialog_stub(language="de")
dialog.language_combo.get_active_id.return_value = "de"
dialog._get_selected_whispercpp_model.return_value = "small"
dialog._get_recommended_whispercpp_model_for_language.return_value = ("small", "reason")
with patch.object(settings_dialog, "_is_following_layout", return_value=True):
dialog_class._sync_simple_from_advanced(dialog)
dialog.simple_language_combo.set_active_id.assert_called_once_with("de")
dialog.simple_language_row.set_sensitive.assert_called_once_with(False)
dialog.simple_language_row.set_tooltip_text.assert_called_once_with(
"Follows your keyboard layout. Turn off Follow keyboard layout "
"in Advanced to pick a language."
)
def test_follow_layout_priority_change_does_not_write_resolved_language(
settings_dialog: Any, dialog_class: Any
) -> None:
dialog = _dialog_stub(language="de", priority=ACCURATE, recommended="small")
dialog._get_selected_engine.return_value = "whisper_cpp"
with patch.object(settings_dialog, "_is_following_layout", return_value=True):
dialog_class._apply_simple_choice(dialog)
dialog._set_combo_active_id_or_first.assert_not_called()
assert dialog.language == "de"
dialog.model_combo.set_active_id.assert_called_once_with("medium")
def test_follow_layout_priority_change_keeps_multilingual_variant_and_mode(
settings_dialog: Any, dialog_class: Any
) -> None:
dialog = _dialog_stub(language="en-us", priority=FASTEST, recommended="small")
dialog._follow_layout_active = True
dialog.follow_layout_switch.get_active.return_value = True
dialog._follow_layout_supported.return_value = True
selected_variant: dict[str, Any] = {"id": None}
dialog.model_variant_combo.set_active_id.side_effect = lambda variant: selected_variant.update(
id=variant
)
dialog._get_selected_whispercpp_model.side_effect = lambda: selected_variant["id"] or "small"
dialog._is_selected_whispercpp_model_english_only.side_effect = lambda: (
settings_dialog.is_english_only_whispercpp_model(dialog._get_selected_whispercpp_model())
)
dialog_class._apply_simple_choice(dialog)
dialog_class._sync_follow_layout_controls(dialog)
assert selected_variant["id"] == "base"
dialog.model_variant_combo.set_active_id.assert_called_once_with("base")
dialog._on_disk_stand_in.assert_called_once_with(
"base", "base", settings_dialog.LANGUAGE_FOLLOWS_LAYOUT
)
assert dialog.follow_layout_switch.get_active() is True
assert dialog._follow_layout_active is True
@pytest.mark.parametrize("stale_second", ["auto", "en-us"])
def test_pinned_advanced_language_plus_stale_stored_second_does_not_replace_the_pin(
settings_dialog, dialog_class, stale_second