From 9ae473355a4cf63cd4761b23addae5e48f4b414c Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Mon, 7 Sep 2026 11:33:10 -0500 Subject: [PATCH] allow smol when beeg --- starpilot/assets/model_manager.py | 13 ++++++- starpilot/assets/tests/test_model_pipeline.py | 34 +++++++++++++++++++ .../assets/components/tools/model_manager.js | 8 ++--- .../system/the_galaxy/assets/mobile/js/api.js | 1 + .../assets/mobile/js/views/ModelManager.js | 32 +++++++++++++++-- .../the_galaxy/tests/test_dashboard_stats.py | 16 ++++++++- .../the_galaxy/tests/test_ui_vue_frontend.py | 2 +- starpilot/system/the_galaxy/the_galaxy.py | 17 +++++++++- system/hardware/hardwared.py | 3 +- 9 files changed, 114 insertions(+), 12 deletions(-) diff --git a/starpilot/assets/model_manager.py b/starpilot/assets/model_manager.py index 13912eae2..ce574f12f 100644 --- a/starpilot/assets/model_manager.py +++ b/starpilot/assets/model_manager.py @@ -37,6 +37,7 @@ ACTIVE_BIG_MODEL_VERSION_PARAM = "ActiveBigModelVersion" ACTIVE_SMALL_MODEL_PARAM = "ActiveSmallModel" ACTIVE_SMALL_MODEL_NAME_PARAM = "ActiveSmallModelName" ACTIVE_SMALL_MODEL_VERSION_PARAM = "ActiveSmallModelVersion" +DISABLED_MODEL_PROFILE = "none" MODEL_PROFILE_PARAMS = { "big": (ACTIVE_BIG_MODEL_PARAM, ACTIVE_BIG_MODEL_NAME_PARAM, ACTIVE_BIG_MODEL_VERSION_PARAM), "small": (ACTIVE_SMALL_MODEL_PARAM, ACTIVE_SMALL_MODEL_NAME_PARAM, ACTIVE_SMALL_MODEL_VERSION_PARAM), @@ -154,7 +155,11 @@ def get_model_profile(params, profile: str) -> tuple[str, str, str]: raise ValueError(f"Unknown model profile: {profile}") key_param, name_param, version_param = MODEL_PROFILE_PARAMS[profile] - model_key = canonical_model_key(_params_text(params, key_param)) + stored_value = _params_text(params, key_param) + if profile == "big" and stored_value.lower() == DISABLED_MODEL_PROFILE: + return "", "", "" + + model_key = canonical_model_key(stored_value) requires_gpu = profile == "big" if model_key and model_uses_external_gpu(model_key) != requires_gpu: model_key = "" @@ -203,6 +208,12 @@ def set_model_profile(params, profile: str, model_key: str, model_name: str = "" params.put(version_param, model_version or catalog_version or ("v15" if is_builtin_model_key(canonical_key) else "")) +def disable_big_model_profile(params) -> None: + params.put(ACTIVE_BIG_MODEL_PARAM, DISABLED_MODEL_PROFILE) + params.remove(ACTIVE_BIG_MODEL_NAME_PARAM) + params.remove(ACTIVE_BIG_MODEL_VERSION_PARAM) + + def set_runtime_model_params(params, model_key: str, model_version: str = "") -> None: canonical_key = canonical_model_key(model_key) or DEFAULT_MODEL_KEY profile = "big" if model_uses_external_gpu(canonical_key) else "small" diff --git a/starpilot/assets/tests/test_model_pipeline.py b/starpilot/assets/tests/test_model_pipeline.py index e3ba5a871..f4ac17371 100644 --- a/starpilot/assets/tests/test_model_pipeline.py +++ b/starpilot/assets/tests/test_model_pipeline.py @@ -177,6 +177,40 @@ def test_active_small_and_big_profiles_migrate_from_legacy_selection(tmp_path, m assert model_manager.get_model_profile(big_params, "big") == ("big-one", "Big One", "v16") +def test_disabled_big_profile_does_not_migrate_from_legacy_selection(tmp_path, monkeypatch): + monkeypatch.setattr(model_manager, "MODELS_PATH", tmp_path) + (tmp_path / model_manager.ARTIFACT_METADATA_CACHE).write_text(json.dumps({ + "big-one": {"uses_external_gpu": True}, + })) + + class FakeParams: + def __init__(self): + self.values = { + "Model": "big-one", + "DrivingModel": "big-one", + "ActiveBigModel": model_manager.DISABLED_MODEL_PROFILE, + "ActiveBigModelName": "Big One", + "ActiveBigModelVersion": "v16", + } + + def get(self, key): + return self.values.get(key) + + def put(self, key, value): + self.values[key] = value + + def remove(self, key): + self.values.pop(key, None) + + params = FakeParams() + assert model_manager.get_model_profile(params, "big") == ("", "", "") + + model_manager.disable_big_model_profile(params) + assert params.values["ActiveBigModel"] == model_manager.DISABLED_MODEL_PROFILE + assert "ActiveBigModelName" not in params.values + assert "ActiveBigModelVersion" not in params.values + + def test_runtime_model_metadata_does_not_overwrite_model_profiles(tmp_path, monkeypatch): monkeypatch.setattr(model_manager, "MODELS_PATH", tmp_path) (tmp_path / model_manager.ARTIFACT_METADATA_CACHE).write_text(json.dumps({ diff --git a/starpilot/system/the_galaxy/assets/components/tools/model_manager.js b/starpilot/system/the_galaxy/assets/components/tools/model_manager.js index cd85704f6..690110771 100644 --- a/starpilot/system/the_galaxy/assets/components/tools/model_manager.js +++ b/starpilot/system/the_galaxy/assets/components/tools/model_manager.js @@ -473,8 +473,8 @@ function bindDomHandlers() { if (!(target instanceof HTMLSelectElement)) return; if (target.id === "mm-active-small-model-select" || target.id === "mm-active-big-model-select") { const modelKey = safeText(target.value, ""); - if (!modelKey) return; const profile = target.id === "mm-active-big-model-select" ? "big" : "small"; + if (!modelKey && profile !== "big") return; runAction(`select-${profile}`, modelKey).catch(() => {}); return; } @@ -669,7 +669,7 @@ export function ModelManager() { Active Big - + ${() => { const orderedInstalled = getInstalledModels("big").sort((a, b) => { const aCurrent = safeText(a.value) === state.activeBigModel ? 0 : 1; @@ -680,14 +680,14 @@ export function ModelManager() { return orderedInstalled.length > 0 ? html` - ${state.activeBigModel ? "" : html`Choose an eGPU model`} + None — always use Active Small ${orderedInstalled.map(model => html` ${safeText(model.label, model.value)} `)} ` - : html`No installed eGPU models`; + : html`None — always use Active Small`; }} diff --git a/starpilot/system/the_galaxy/assets/mobile/js/api.js b/starpilot/system/the_galaxy/assets/mobile/js/api.js index 19cfcda8a..2fb5de5bc 100644 --- a/starpilot/system/the_galaxy/assets/mobile/js/api.js +++ b/starpilot/system/the_galaxy/assets/mobile/js/api.js @@ -260,6 +260,7 @@ export const api = { sentryPushSubscribe(body) { return request("/api/sentry/push/subscribe", { method: "POST", data: body }) }, getModelStatus() { return requestOk("/api/models/status", { cache: "no-store" }) }, + setActiveModel(profile, modelKey = "") { return request("/api/models/active", { method: "PUT", data: { profile, model: modelKey } }) }, startModelDownload(modelKey, allowGpuWithoutGpu = false) { return request("/api/models/download", { method: "POST", data: { model: modelKey, allowGpuWithoutGpu } }) }, downloadAllModels(allowGpuWithoutGpu = false) { return request("/api/models/download_all", { method: "POST", data: { allowGpuWithoutGpu } }) }, deleteModel(modelKey) { return request("/api/models/delete", { method: "POST", data: { model: modelKey } }) }, diff --git a/starpilot/system/the_galaxy/assets/mobile/js/views/ModelManager.js b/starpilot/system/the_galaxy/assets/mobile/js/views/ModelManager.js index 3d3c13290..f9570ec3a 100644 --- a/starpilot/system/the_galaxy/assets/mobile/js/views/ModelManager.js +++ b/starpilot/system/the_galaxy/assets/mobile/js/views/ModelManager.js @@ -24,6 +24,8 @@ export const ModelManager = { allowGpu: false, models: [], currentModel: "", + activeSmallModel: "", + activeBigModel: "", summary: { installed: 0, missing: 0, total: 0 }, status: { modelToDownload: "", @@ -41,6 +43,12 @@ export const ModelManager = { const match = (this.models || []).find((m) => text(m && m.value, "") === key) return match ? text(match.label, key) : key }, + installedSmallModels() { + return (this.models || []).filter((m) => m?.installed && !m?.requiresGpu) + }, + installedBigModels() { + return (this.models || []).filter((m) => m?.installed && !!m?.requiresGpu) + }, sorted() { const mode = this.sortMode const rows = (this.models || []) @@ -95,6 +103,8 @@ export const ModelManager = { const p = await api.getModelStatus() this.models = Array.isArray(p.models) ? p.models.filter((m) => m && typeof m === "object") : [] this.currentModel = text(p.currentModel, "") + this.activeSmallModel = text(p.activeSmallModel, "") + this.activeBigModel = text(p.activeBigModel, "") const s = p.summary && typeof p.summary === "object" ? p.summary : {} this.summary = { installed: Number(s.installed) || 0, @@ -129,8 +139,9 @@ export const ModelManager = { this.busy = `${action}:${key}` try { let msg = "" - if (action === "select") { - const p = await api.updateParam({ key: "Model", value: key }) + if (action === "select-small" || action === "select-big") { + const profile = action === "select-big" ? "big" : "small" + const p = await api.setActiveModel(profile, key) msg = p?.message || `Selected "${label}".` } else if (action === "download") { const p = await api.startModelDownload(key, this.allowGpu) @@ -222,6 +233,21 @@ export const ModelManager = { Refresh + + Active Small + m.value === $event.target.value))"> + {{ m.label || m.value }} + + + + + Active Big + m.value === $event.target.value)) : runAction('select-big')"> + None — always use Active Small + {{ m.label || m.value }} + + + Sort @@ -294,7 +320,7 @@ export const ModelManager = { Cancel - Set Active + Set Active {{ m.requiresGpu ? 'Big' : 'Small' }} Delete diff --git a/starpilot/system/the_galaxy/tests/test_dashboard_stats.py b/starpilot/system/the_galaxy/tests/test_dashboard_stats.py index 2d16a3d64..c2904dbdc 100644 --- a/starpilot/system/the_galaxy/tests/test_dashboard_stats.py +++ b/starpilot/system/the_galaxy/tests/test_dashboard_stats.py @@ -44,9 +44,17 @@ model_manager = ModuleType("openpilot.starpilot.assets.model_manager") model_manager.MODEL_LAB_DOWNLOAD_PARAM = "ModelLabModelToDownload" model_manager.canonical_model_key = lambda value: str(value or "").strip().lower().replace(" ", "-") model_manager.external_gpu_available = lambda: False +model_manager.disable_big_model_profile = lambda params: ( + params.put("ActiveBigModel", "none"), + params.remove("ActiveBigModelName"), + params.remove("ActiveBigModelVersion"), +) def _stub_get_model_profile(params, profile): prefix = "ActiveBigModel" if profile == "big" else "ActiveSmallModel" - key = params.get(prefix) or ("rdf43" if profile == "small" else "") + stored_key = params.get(prefix) + if profile == "big" and stored_key == "none": + return "", "", "" + key = stored_key or ("rdf43" if profile == "small" else "") return key, params.get(f"{prefix}Name") or key, params.get(f"{prefix}Version") or "" @@ -1851,6 +1859,12 @@ def test_model_profiles_can_be_selected_without_external_gpu(monkeypatch, tmp_pa assert status["activeBigModel"] == "big-one" assert status["activeSmallModel"] == "small-one" + disabled = client.put("/api/models/active", json={"profile": "big", "model": ""}) + assert disabled.status_code == 200 + assert params.values["ActiveBigModel"] == "none" + assert disabled.get_json()["model"] == "" + assert client.get("/api/models/status").get_json()["activeBigModel"] == "" + wrong_profile = client.put("/api/models/active", json={"profile": "small", "model": "big-one"}) assert wrong_profile.status_code == 409 diff --git a/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py b/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py index 28b3dc048..589bdb32f 100644 --- a/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py +++ b/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py @@ -425,7 +425,7 @@ def test_ui_all_remaining_classic_tools_native_no_embed(): # Shared API surface added for the second batch of ported pages. for method in ["selectTestingGround", "getSentryStatus", "getSentryEvents", "deleteSentryEvent", "sentryPushSubscribe", - "getModelStatus", "startModelDownload", "downloadAllModels", "deleteModel", "saveModelPreferences", + "getModelStatus", "setActiveModel", "startModelDownload", "downloadAllModels", "deleteModel", "saveModelPreferences", "getPlotsLive", "getGalaxySession", "deleteNavigationKey", "getThemeList", "saveTheme", "applyTheme", "deleteTheme", "downloadTheme", diff --git a/starpilot/system/the_galaxy/the_galaxy.py b/starpilot/system/the_galaxy/the_galaxy.py index 66d52fb39..d3351b17b 100644 --- a/starpilot/system/the_galaxy/the_galaxy.py +++ b/starpilot/system/the_galaxy/the_galaxy.py @@ -55,6 +55,7 @@ from panda import Panda from openpilot.starpilot.assets.model_manager import ( MODEL_LAB_DOWNLOAD_PARAM, canonical_model_key, + disable_big_model_profile, external_gpu_available, get_model_profile, is_builtin_model_key, @@ -6450,7 +6451,21 @@ def setup(app): model_key = canonical_model_key(str(data.get("model") or "").strip()) if not model_key: - return jsonify({"error": "Missing model key."}), 400 + if profile != "big": + return jsonify({"error": "Active Small cannot be disabled."}), 400 + + lab_config = normalize_model_lab_config(params.get(MODEL_LAB_CONFIG_PARAM, encoding="utf-8") or "") + if lab_config["enabled"]: + lab_config["enabled"] = False + params.put(MODEL_LAB_CONFIG_PARAM, lab_config) + params.remove(MODEL_LAB_RUNTIME_PARAM) + + disable_big_model_profile(params) + return jsonify({ + "message": "Active Big disabled. Active Small will be used even when Chestnut is connected.", + "profile": profile, + "model": "", + }), 200 catalog = {model["value"]: model for model in get_model_catalog()} model = catalog.get(model_key) diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index 9463df11d..648350c3a 100644 --- a/system/hardware/hardwared.py +++ b/system/hardware/hardwared.py @@ -428,7 +428,8 @@ def hardware_thread(end_event, hw_queue) -> None: if chestnut is not None: chestnut.update(started_ts is None, last_hw_state.usb_state) model_lab_config = params.get("ModelLabConfig") - chestnut_expected = bool(params.get("ActiveBigModel")) or ( + active_big_model = params.get("ActiveBigModel", encoding="utf-8") or "" + chestnut_expected = active_big_model.lower() not in ("", "none") or ( isinstance(model_lab_config, dict) and bool(model_lab_config.get("enabled")) ) chestnut_state = sm["chestnutState"]