diff --git a/common/params_keys.h b/common/params_keys.h index 1f8bfcc43..b0e46eb53 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -310,6 +310,7 @@ inline static std::unordered_map keys = { {"DeveloperSidebarMetric7", {PERSISTENT, INT, "7", "0", 3}}, {"DeveloperUI", {PERSISTENT, BOOL, "0", "0", 3}}, {"GalaxyDeveloperMode", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_SIMPLE}}, + {"GalaxyMobileDefault", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_ADVANCED}}, {"DeveloperWidgets", {PERSISTENT, BOOL, "1", "0", 3}}, {"DeviceManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}}, {"DeviceShutdown", {PERSISTENT, INT, "6", "6", 1, SETTINGS_SIMPLE}}, diff --git a/starpilot/common/assets/device_settings_layout.json b/starpilot/common/assets/device_settings_layout.json index 57f6093e8..ee2bf452e 100644 --- a/starpilot/common/assets/device_settings_layout.json +++ b/starpilot/common/assets/device_settings_layout.json @@ -4914,6 +4914,15 @@ "is_parent_toggle": true, "settings_tier": "simple" }, + { + "key": "GalaxyMobileDefault", + "label": "Try the Big Dipper Web UI", + "description": "Open the Big Dipper at the top-level Galaxy link instead of the classic Galaxy. The classic UI remains available at /classic and Big Dipper at /mobile regardless of this toggle.", + "picker_description": "Serve the Big Dipper as the default landing page.", + "data_type": "bool", + "ui_type": "toggle", + "settings_tier": "advanced" + }, { "key": "AlphaLongitudinalEnabled", "label": "openpilot Longitudinal Control (Alpha)", diff --git a/starpilot/system/the_galaxy/assets/mobile/css/material.css b/starpilot/system/the_galaxy/assets/mobile/css/material.css index 440c6df31..17df4da62 100644 --- a/starpilot/system/the_galaxy/assets/mobile/css/material.css +++ b/starpilot/system/the_galaxy/assets/mobile/css/material.css @@ -342,6 +342,10 @@ ul { list-style: none; margin: 0; padding: 0; } .gx-appbar__back:active { transform: scale(0.92); } .gx-appbar__title { + background: linear-gradient(135deg, #8b6cc5 0%, #5ec8c8 55%, #d4789c 100%); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; font-size: var(--fs-lg); font-weight: var(--fw-bold); white-space: nowrap; diff --git a/starpilot/system/the_galaxy/assets/mobile/index.html b/starpilot/system/the_galaxy/assets/mobile/index.html index 6bad72762..71031659e 100644 --- a/starpilot/system/the_galaxy/assets/mobile/index.html +++ b/starpilot/system/the_galaxy/assets/mobile/index.html @@ -7,7 +7,7 @@ - + @@ -26,7 +26,7 @@ } - Galaxy + Big Dipper diff --git a/starpilot/system/the_galaxy/assets/mobile/js/api.js b/starpilot/system/the_galaxy/assets/mobile/js/api.js index db4bbf89f..19cfcda8a 100644 --- a/starpilot/system/the_galaxy/assets/mobile/js/api.js +++ b/starpilot/system/the_galaxy/assets/mobile/js/api.js @@ -105,11 +105,38 @@ export const api = { deleteAllRoutes(includePreserved) { return request(`/api/routes/delete_all?include_preserved=${includePreserved}`, { method: "DELETE" }) }, getRouteLogs(name) { return request(`/api/routes/${encodeURIComponent(name)}/logs`) }, - getScreenRecordings() { return request("/api/screen_recordings/list") }, + async screenRecordingsStream({ onProgress, onRecordings, signal } = {}) { + const res = await fetch("/api/screen_recordings/list", { signal }) + if (!res.ok || !res.body) throw new Error(`Screen recordings request failed (${res.status})`) + const reader = res.body.getReader() + const decoder = new TextDecoder() + let buffer = "" + while (true) { + const { value, done } = await reader.read() + if (done) break + buffer += decoder.decode(value, { stream: true }) + const events = buffer.split(/\r?\n\r?\n/) + buffer = events.pop() || "" + for (const event of events) { + const lines = event.split(/\r?\n/).filter((l) => l.startsWith("data:")) + if (!lines.length) continue + try { + const payload = JSON.parse(lines.map((l) => l.slice(5).trimStart()).join("\n")) + if (Number.isFinite(payload.progress)) onProgress?.(payload.progress) + onRecordings?.(Array.isArray(payload.recordings) ? payload.recordings : []) + } catch (e) { } + } + } + }, + screenRecordingVideoUrl(filename) { return `/api/screen_recordings/download/${encodeURIComponent(filename)}` }, deleteScreenRecording(filename) { return request(`/api/screen_recordings/delete/${encodeURIComponent(filename)}`, { method: "DELETE" }) }, deleteAllScreenRecordings() { return request("/api/screen_recordings/delete_all", { method: "DELETE" }) }, renameScreenRecording(oldName, newName) { return request("/api/screen_recordings/rename", { method: "POST", data: { old: oldName, new: newName } }) }, + getModelLab() { return request("/api/model-laboratory", { cache: "no-store" }) }, + saveModelLab(config) { return request("/api/model-laboratory", { method: "PUT", data: config }) }, + prepareModelLabArtifact(model) { return request("/api/model-laboratory/download", { method: "POST", data: { model } }) }, + getErrorLogs() { return request("/api/error_logs", { headers: { Accept: "application/json" } }) }, getErrorLog(filename) { return fetch(`/api/error_logs/${encodeURIComponent(filename)}`).then((r) => r.text()) }, deleteErrorLog(filename) { return delOk(`/api/error_logs/${encodeURIComponent(filename)}`) }, diff --git a/starpilot/system/the_galaxy/assets/mobile/js/app.js b/starpilot/system/the_galaxy/assets/mobile/js/app.js index ca3d38a62..7976f4c7f 100644 --- a/starpilot/system/the_galaxy/assets/mobile/js/app.js +++ b/starpilot/system/the_galaxy/assets/mobile/js/app.js @@ -18,6 +18,7 @@ import { ModelManager } from "./views/ModelManager.js" import { Plots } from "./views/Plots.js" import { TestingGround } from "./views/TestingGround.js" import { ThemeMaker } from "./views/ThemeMaker.js" +import { ModelLaboratory } from "./views/ModelLaboratory.js" import { Cameras } from "./views/Cameras.js" import { store, initRouter, navigate } from "./store.js" import { showSnackbar } from "./api.js" @@ -52,6 +53,7 @@ const VIEWS = { "/plots": Plots, "/testing_ground": TestingGround, "/theme_maker": ThemeMaker, + "/model_laboratory": ModelLaboratory, "/cameras": Cameras, } diff --git a/starpilot/system/the_galaxy/assets/mobile/js/components/AppShell.js b/starpilot/system/the_galaxy/assets/mobile/js/components/AppShell.js index d7297c785..630d30a3f 100644 --- a/starpilot/system/the_galaxy/assets/mobile/js/components/AppShell.js +++ b/starpilot/system/the_galaxy/assets/mobile/js/components/AppShell.js @@ -102,7 +102,7 @@ export const AppShell = { - Galaxy + Big Dipper
@@ -128,8 +128,8 @@ export const AppShell = {
diff --git a/starpilot/system/the_galaxy/assets/mobile/js/views/Vasm.js b/starpilot/system/the_galaxy/assets/mobile/js/views/Vasm.js index 85cfec4cf..21c182832 100644 --- a/starpilot/system/the_galaxy/assets/mobile/js/views/Vasm.js +++ b/starpilot/system/the_galaxy/assets/mobile/js/views/Vasm.js @@ -398,10 +398,10 @@ export const Vasm = {
- Left: {{ leftPoints.length }} pt{{ leftPoints.length !== 1 ? 's' : '' }} + Left: {{ leftPoints.length }} pt{{ leftPoints.length !== 1 ? 's' : '' }} × - Right: {{ rightPoints.length }} pt{{ rightPoints.length !== 1 ? 's' : '' }} + Right: {{ rightPoints.length }} pt{{ rightPoints.length !== 1 ? 's' : '' }} ×
diff --git a/starpilot/system/the_galaxy/assets/mobile/manifest.json b/starpilot/system/the_galaxy/assets/mobile/manifest.json index 3fdbe29c9..8ad93b911 100644 --- a/starpilot/system/the_galaxy/assets/mobile/manifest.json +++ b/starpilot/system/the_galaxy/assets/mobile/manifest.json @@ -1,6 +1,6 @@ { - "name": "Galaxy", - "short_name": "Galaxy", + "name": "Big Dipper", + "short_name": "Big Dipper", "description": "Control and configure your openpilot device from anywhere.", "icons": [ { "src": "/assets/images/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" }, 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 ced2e64b2..28b3dc048 100644 --- a/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py +++ b/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py @@ -325,15 +325,17 @@ def test_ui_galaxy_background_is_css_only_and_lightweight(): def test_galaxy_py_serves_classic_at_root_and_new_ui_at_mobile(): source = GALAXY_PY.read_text(encoding="utf-8") - # The classic Galaxy SPA is the default landing at / (original behaviour). + # The classic Galaxy SPA is the default landing at / (original behaviour) unless + # the "New Galaxy by Default" (GalaxyMobileDefault) toggle is enabled. assert '@app.route("/", methods=["GET"])' in source assert 'render_template("index.html")' in source + assert 'params.get_bool("GalaxyMobileDefault")' in source # Classic also stays reachable at /classic (page-in-page embed target). assert '@app.route("/classic", methods=["GET"])' in source - # The modern Vue UI is served at /mobile (and /ui), not the root. + # The modern Vue UI is served at /mobile, not the root. assert '@app.route("/mobile", methods=["GET"])' in source - assert '@app.route("/ui", methods=["GET"])' in source assert 'Path(app.static_folder) / "mobile" / "index.html"' in source + assert '@app.route("/ui", methods=["GET"])' not in source def test_ui_manifest_is_valid_pwa_manifest(): diff --git a/starpilot/system/the_galaxy/the_galaxy.py b/starpilot/system/the_galaxy/the_galaxy.py index 87b94f54f..2c2a8e9bc 100644 --- a/starpilot/system/the_galaxy/the_galaxy.py +++ b/starpilot/system/the_galaxy/the_galaxy.py @@ -5075,6 +5075,8 @@ def setup(app): @app.route("/", methods=["GET"]) def index(): + if params.get_bool("GalaxyMobileDefault"): + return _serve_new_ui() response = make_response(render_template("index.html")) response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0" response.headers["Pragma"] = "no-cache" @@ -5088,8 +5090,6 @@ def setup(app): @app.route("/mobile", methods=["GET"]) @app.route("/mobile/", methods=["GET"]) - @app.route("/ui", methods=["GET"]) - @app.route("/ui/", methods=["GET"]) def mobile_index(): return _serve_new_ui()