Этап 1: трей, окно чата и текстовый диалог с LLM

- PySide6: значок в трее с индикацией состояния, окно чата со стримингом и markdown
- Единый OpenAI-совместимый клиент (Ollama, LM Studio, OpenRouter)
- config.yaml с автосозданием, слиянием с умолчаниями и подстановкой ${ENV}
- Тесты конфига и сборки сообщений

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nid
2026-09-17 03:04:21 +03:00
co-authored by Claude Opus 5
parent 26a01aa210
commit 7933613234
19 changed files with 1574 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
from pathlib import Path
import pytest
from agr_assistent.config import ConfigError, load_config
def _write(path: Path, text: str) -> Path:
path.write_text(text, encoding="utf-8")
return path
def test_creates_default_config_when_missing(tmp_path: Path) -> None:
path = tmp_path / "config.yaml"
config = load_config(path)
assert path.exists()
assert config.llm.provider == "ollama"
assert config.llm.active_provider.base_url == "http://localhost:11434/v1"
def test_user_values_override_defaults(tmp_path: Path) -> None:
path = _write(
tmp_path / "config.yaml",
"llm:\n provider: openrouter\n providers:\n openrouter:\n model: my/model\n",
)
config = load_config(path)
assert config.llm.active_provider.model == "my/model"
assert config.llm.active_provider.base_url == "https://openrouter.ai/api/v1"
assert config.llm.temperature == 0.7
def test_api_key_expands_environment_variable(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
path = _write(
tmp_path / "config.yaml",
"llm:\n providers:\n openrouter:\n api_key: ${TEST_AGR_KEY}\n",
)
monkeypatch.setenv("TEST_AGR_KEY", "secret")
assert load_config(path).llm.providers["openrouter"].api_key == "secret"
monkeypatch.delenv("TEST_AGR_KEY")
assert load_config(path).llm.providers["openrouter"].api_key == ""
def test_unknown_provider_is_rejected(tmp_path: Path) -> None:
path = _write(tmp_path / "config.yaml", "llm:\n provider: nope\n")
with pytest.raises(ConfigError, match="nope"):
load_config(path)
def test_invalid_yaml_is_rejected(tmp_path: Path) -> None:
path = _write(tmp_path / "config.yaml", "llm: [unclosed\n")
with pytest.raises(ConfigError):
load_config(path)