Этап 5: окно настроек и сборка exe
- Окно настроек: модель и провайдер (со списком моделей с сервера), озвучка,
голосовой ввод, слово активации, автозапуск с Windows
- Сохранение в config.yaml через ruamel.yaml с комментариями и ссылками ${VAR},
проверка значений до записи; переключатели трея тоже сохраняются
- Настройки LLM и переключатели применяются на лету, для остального — перезапуск
- Один экземпляр приложения, лог в файл, аргументы --config и --wait-pid, русские диалоги Qt
- Сборка PyInstaller (onedir) с библиотеками CUDA, иконка приложения
- Тесты сохранения настроек и окна настроек
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a719980d74
commit
20a666e0d4
@@ -0,0 +1,85 @@
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from agr_assistent.config import ConfigError, load_config, save_config_updates
|
||||
from agr_assistent.core.settings import Settings, needs_restart
|
||||
from agr_assistent.ui.settings_dialog import SettingsDialog
|
||||
|
||||
_USER_CONFIG = """\
|
||||
# мой конфиг
|
||||
llm:
|
||||
provider: ollama # локально
|
||||
providers:
|
||||
openrouter:
|
||||
api_key: ${TEST_SETTINGS_KEY}
|
||||
"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config_path(tmp_path: Path) -> Path:
|
||||
path = tmp_path / "config.yaml"
|
||||
path.write_text(_USER_CONFIG, encoding="utf-8")
|
||||
return path
|
||||
|
||||
|
||||
def test_save_keeps_comments_and_env_references(config_path: Path) -> None:
|
||||
config = save_config_updates(
|
||||
config_path, {"llm.provider": "openrouter", "wake_word.phrases": ["эй", "компьютер"]}
|
||||
)
|
||||
|
||||
text = config_path.read_text(encoding="utf-8")
|
||||
assert "# мой конфиг" in text
|
||||
assert "provider: openrouter # локально" in text
|
||||
assert "api_key: ${TEST_SETTINGS_KEY}" in text
|
||||
assert config.llm.provider == "openrouter"
|
||||
assert load_config(config_path).wake_word.phrases == ["эй", "компьютер"]
|
||||
|
||||
|
||||
def test_invalid_update_does_not_touch_file(config_path: Path) -> None:
|
||||
with pytest.raises(ConfigError):
|
||||
save_config_updates(config_path, {"llm.provider": "nope"})
|
||||
|
||||
assert config_path.read_text(encoding="utf-8") == _USER_CONFIG
|
||||
|
||||
|
||||
def test_restart_is_needed_only_for_heavy_settings(config_path: Path) -> None:
|
||||
config = load_config(config_path)
|
||||
|
||||
live = replace(config, tts=replace(config.tts, enabled=False))
|
||||
live.llm = replace(config.llm, temperature=0.1)
|
||||
assert not needs_restart(config, live)
|
||||
assert needs_restart(config, replace(config, voice=replace(config.voice, hotkey="f9")))
|
||||
assert needs_restart(config, replace(config, tts=replace(config.tts, speaker="aidar")))
|
||||
|
||||
|
||||
def test_dialog_saves_only_changed_values(
|
||||
qapp: QApplication, config_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
monkeypatch.setenv("TEST_SETTINGS_KEY", "secret")
|
||||
settings = Settings(load_config(config_path))
|
||||
changes: list[object] = []
|
||||
settings.changed.connect(changes.append)
|
||||
dialog = SettingsDialog(settings)
|
||||
|
||||
# Переключаемся на openrouter: ключ показан ссылкой на переменную, а не секретом
|
||||
dialog._provider.setCurrentText("openrouter")
|
||||
assert dialog._api_key.text() == "${TEST_SETTINGS_KEY}"
|
||||
dialog._model.setEditText("anthropic/claude-sonnet-5")
|
||||
dialog._tts_enabled.setChecked(False)
|
||||
|
||||
assert dialog._collect() == {
|
||||
"llm.provider": "openrouter",
|
||||
"llm.providers.openrouter.model": "anthropic/claude-sonnet-5",
|
||||
"tts.enabled": False,
|
||||
}
|
||||
|
||||
dialog._save()
|
||||
|
||||
text = config_path.read_text(encoding="utf-8")
|
||||
assert "secret" not in text
|
||||
assert settings.config.llm.active_provider.model == "anthropic/claude-sonnet-5"
|
||||
assert settings.config.llm.active_provider.api_key == "secret"
|
||||
assert len(changes) == 1
|
||||
Reference in New Issue
Block a user