diff --git a/bettercode/gui/app.py b/bettercode/gui/app.py index 01a0c29..a3e46a5 100644 --- a/bettercode/gui/app.py +++ b/bettercode/gui/app.py @@ -57,20 +57,23 @@ def __init__(self, master, callbacks=None): def save_anthropic(self): set_api_key("anthropic", self.ant_key.get()) - if self.callbacks: self.callbacks() + if self.callbacks: + self.callbacks() self.destroy() - + def save_openai(self): set_api_key("openai", self.oai_key.get()) - if self.callbacks: self.callbacks() + if self.callbacks: + self.callbacks() self.destroy() - + def save_sub(self): try: login(self.sub_user.get(), self.sub_pass.get()) - if self.callbacks: self.callbacks() + if self.callbacks: + self.callbacks() self.destroy() - except: + except Exception: pass class BetterCodeApp(ctk.CTk): @@ -189,7 +192,8 @@ def open_auth(self): def handle_send(self, event=None): text = self.chat_input.get().strip() - if not text: return + if not text: + return if not self.current_workspace_id: self.append_chat("system", "Please select a workspace first.") diff --git a/bettercode/settings.py b/bettercode/settings.py index 35aeb08..5efac79 100644 --- a/bettercode/settings.py +++ b/bettercode/settings.py @@ -4,12 +4,12 @@ import threading from pathlib import Path -_logger = logging.getLogger(__name__) - from bettercode.app_meta import bettercode_home_dir from bettercode.i18n import detect_system_human_language, normalize_human_language from bettercode.updater import normalize_version_tag +_logger = logging.getLogger(__name__) + COST_TIER_ORDER = ("low", "medium", "high") AUTO_MODEL_PREFERENCE_ORDER = ("balanced", "cheaper", "faster", "smarter") diff --git a/bettercode/web/api.py b/bettercode/web/api.py index b0cf6e3..5e6aec2 100644 --- a/bettercode/web/api.py +++ b/bettercode/web/api.py @@ -37,7 +37,6 @@ from bettercode.context import ( CodeReview, Message, - MemoryEntry, RouterTelemetry, SessionLocal, Workspace, @@ -62,11 +61,11 @@ open_generated_file_payload as _open_generated_file_payload_base, open_telemetry_log_payload as _open_telemetry_log_payload_base, ) -from bettercode.web.bootstrap import _require_selector_for_app_startup, _start_selector_warmup +from bettercode.web.bootstrap import _require_selector_for_app_startup, _start_selector_warmup # noqa: F401 from bettercode.web.chat_processes import ( + ACTIVE_CHAT_PROCESS_META, # noqa: F401 + ACTIVE_CHAT_PROCESS_META_LOCK, # noqa: F401 ACTIVE_CHAT_PROCESSES, - ACTIVE_CHAT_PROCESS_META, - ACTIVE_CHAT_PROCESS_META_LOCK, ACTIVE_CHAT_PROCESSES_LOCK, PENDING_CHAT_INPUT, PENDING_CHAT_INPUT_LOCK, @@ -86,7 +85,6 @@ _manual_task_analysis as _manual_task_analysis_base, ) from bettercode.web.generated_paths import ( - GENERATED_FILES_DIRNAME, _bettercode_home_dir, _resolve_generated_file_path, _workspace_generated_dir, @@ -142,7 +140,7 @@ stop_managed_ollama, suggest_follow_up_recommendations, ) -from bettercode.updater import check_for_updates, normalize_sha256, normalize_update_platform +from bettercode.updater import normalize_sha256, normalize_update_platform STATIC_DIR = Path(__file__).with_name("static") diff --git a/bettercode/web/desktop.py b/bettercode/web/desktop.py index ce90e8e..a0b3fd8 100644 --- a/bettercode/web/desktop.py +++ b/bettercode/web/desktop.py @@ -16,13 +16,12 @@ APP_BUNDLE_ID, APP_ICON_PATH, APP_ICON_PNG_PATH, - APP_ICNS_PATH, APP_NAME, APP_SLUG, APP_TRAY_ICON_PATH, ) from .api import create_app -from .bootstrap import _start_selector_runtime_warmup, _warm_selector_runtime_best_effort +from .bootstrap import _start_selector_runtime_warmup, _warm_selector_runtime_best_effort # noqa: F401 class EmbeddedServer(uvicorn.Server): diff --git a/tests/test_main.py b/tests/test_main.py index e925fff..b9621be 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,6 +1,5 @@ from unittest.mock import Mock import plistlib -from pathlib import Path import bettercode.main as main_module diff --git a/tests/test_selector.py b/tests/test_selector.py index 1534685..03a25c1 100644 --- a/tests/test_selector.py +++ b/tests/test_selector.py @@ -1,8 +1,15 @@ import json +import pytest + from bettercode.router import selector +@pytest.fixture(autouse=True) +def _local_preprocess_enabled(monkeypatch): + monkeypatch.setattr("bettercode.router.selector.get_local_preprocess_mode", lambda: "tiny") + + def test_selector_status_reports_missing_ollama(monkeypatch): monkeypatch.setattr("bettercode.router.selector._ollama_command", lambda: None) @@ -322,6 +329,7 @@ def test_top_candidates_preserve_cross_provider_mix(): def test_select_best_model_local_router_prompt_uses_task_fit_metadata(monkeypatch): + monkeypatch.setattr("bettercode.router.selector.get_local_preprocess_mode", lambda: "small") monkeypatch.setattr("bettercode.router.selector.ensure_selector_runtime", lambda **kwargs: { "installed": True, "running": True, @@ -577,6 +585,7 @@ def test_select_best_model_uses_intent_gate_for_simple_requests(monkeypatch): def test_plan_subtasks_skips_local_breakdown_for_simple_requests(monkeypatch): + monkeypatch.setattr("bettercode.router.selector.get_local_preprocess_mode", lambda: "small") monkeypatch.setattr( "bettercode.router.selector.ensure_selector_runtime", lambda **kwargs: (_ for _ in ()).throw(AssertionError("planner should be skipped")), @@ -691,6 +700,7 @@ def fake_api_request(method, path, payload=None, timeout=5.0): def test_suggest_follow_up_recommendations_uses_full_reply_and_allows_custom_prompt(monkeypatch): + monkeypatch.setattr("bettercode.router.selector.get_local_preprocess_mode", lambda: "small") monkeypatch.setattr("bettercode.router.selector.ensure_selector_runtime", lambda **kwargs: { "installed": True, "running": True, @@ -721,6 +731,7 @@ def fake_api_request(method, path, payload=None, timeout=5.0): def test_plan_subtasks_fallback_includes_stage_metadata(monkeypatch): + monkeypatch.setattr("bettercode.router.selector.get_local_preprocess_mode", lambda: "small") monkeypatch.setattr("bettercode.router.selector.ensure_selector_runtime", lambda **kwargs: { "installed": False, "running": False, @@ -741,6 +752,7 @@ def test_plan_subtasks_fallback_includes_stage_metadata(monkeypatch): def test_plan_subtasks_normalizes_local_router_stage_and_model(monkeypatch): + monkeypatch.setattr("bettercode.router.selector.get_local_preprocess_mode", lambda: "small") monkeypatch.setattr("bettercode.router.selector.ensure_selector_runtime", lambda **kwargs: { "installed": True, "running": True, @@ -797,6 +809,7 @@ def fake_api_request(method, path, payload=None, timeout=5.0): def test_plan_subtasks_distributes_tracks_across_multiple_models(monkeypatch): + monkeypatch.setattr("bettercode.router.selector.get_local_preprocess_mode", lambda: "small") monkeypatch.setattr("bettercode.router.selector.ensure_selector_runtime", lambda **kwargs: { "installed": True, "running": True, diff --git a/tests/test_web_api.py b/tests/test_web_api.py index b388020..f085919 100644 --- a/tests/test_web_api.py +++ b/tests/test_web_api.py @@ -1,5 +1,4 @@ import asyncio -import hashlib import subprocess import json from datetime import UTC, datetime, timedelta @@ -70,6 +69,7 @@ def _selector_runtime_ready(monkeypatch): web_api.GIT_STATUS_CACHE.clear() with web_api.RECENT_FILE_ENTRIES_CACHE_LOCK: web_api.RECENT_FILE_ENTRIES_CACHE.clear() + web_api._clear_model_discovery_cache() monkeypatch.setattr( web_api, "require_selector_runtime", @@ -902,8 +902,13 @@ def fake_run_git(workspace_path, args, check=True): def test_chat_endpoint_persists_messages(monkeypatch, tmp_path): monkeypatch.chdir(tmp_path) monkeypatch.setenv("BETTERCODE_HOME", str(tmp_path / ".bettercode")) - monkeypatch.setitem(web_api.MODEL_DISCOVERY_CACHE, "options", None) - monkeypatch.setitem(web_api.MODEL_DISCOVERY_CACHE, "registry", None) + monkeypatch.setitem(web_api.MODEL_DISCOVERY_CACHE, "options", [ + {"id": "smart", "label": "Auto Model Select"}, + {"id": "codex/gpt-5", "label": "GPT-5"}, + ]) + monkeypatch.setitem(web_api.MODEL_DISCOVERY_CACHE, "registry", [ + {"id": "codex/gpt-5", "label": "GPT-5", "provider": "openai", "runtime": "codex"}, + ]) monkeypatch.setattr("bettercode.web.api.manage_workspace_context", lambda db, workspace: False) monkeypatch.setattr("bettercode.web.api._cli_runtimes", lambda: { "codex": {"available": True, "path": "/usr/bin/codex", "configured": True}, @@ -1607,8 +1612,6 @@ def test_app_update_endpoint_reports_updates_disabled(monkeypatch, tmp_path): def test_app_update_payload_does_not_fetch_updates_when_disabled(monkeypatch): - monkeypatch.setattr(web_api, "check_for_updates", lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("should not fetch real updates"))) - payload = web_api.app_update_payload(force_refresh=True) assert payload == { @@ -1662,6 +1665,7 @@ def test_app_info_includes_configured_models_when_cache_is_empty(monkeypatch, tm monkeypatch.setenv("BETTERCODE_HOME", str(tmp_path / ".bettercode")) init_db() web_api._clear_model_discovery_cache() + monkeypatch.setattr("bettercode.web.api._start_model_discovery_warmup", lambda verified=False: None) monkeypatch.setattr( "bettercode.web.api._cli_runtimes", lambda quick=False, force_refresh=False: {