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() { - ${() => { 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``} + ${orderedInstalled.map(model => html` `)} ` - : html``; + : html``; }} 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 = { +
+ Active Small + +
+ +
+ Active Big + +
+
Sort