mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-04 19:43:49 +08:00
Restore FLM Results Navigation
This commit is contained in:
@@ -58,13 +58,27 @@ assert.doesNotMatch(FlmPage.template, /v-html|Analyze selected|GM Trial|Choose F
|
||||
assert.match(FlmPage.template, /Currently applied/)
|
||||
assert.match(FlmPage.template, /PID unsupported/)
|
||||
const app = readFileSync(new URL("../web/js/app.js", import.meta.url), "utf8")
|
||||
assert(app.includes("['/tuning', '/tuning/plots'].includes(route.path)"))
|
||||
assert(app.includes("['/tuning', '/tuning/plots', '/tuning/flm'].includes(route.path)"))
|
||||
const analysis = app.match(/<div v-else-if="\['\/tuning', '\/tuning\/plots', '\/tuning\/flm'\]\.includes\(route.path\)"[\s\S]*?<\/div>/)?.[0]
|
||||
assert(analysis, 'Existing analysis route and old bookmarks share one page')
|
||||
assert.match(analysis, /<PlotsPage /)
|
||||
assert.match(analysis, /<FlmPage embedded /)
|
||||
assert.doesNotMatch(app, /<FlmPage v-else-if/)
|
||||
compile(analysis.replace("v-else-if", "v-if"), { onError: error => { throw error }, decodeEntities: value => value.replaceAll("&", "&") })
|
||||
assert.doesNotMatch(app, /Recorded-drive analysis/)
|
||||
const settings = readFileSync(new URL("../web/js/settings.js", import.meta.url), "utf8")
|
||||
assert.doesNotMatch(settings, /FLMTrainingResults|View FLM training results/)
|
||||
const catalog = JSON.parse(readFileSync(new URL("../web/data/catalog.json", import.meta.url), "utf8"))
|
||||
assert(catalog.tools.some(tool => tool.path === "/tuning/plots"))
|
||||
assert(catalog.tools.some(tool => tool.path === "/tuning/flm"))
|
||||
assert(!catalog.tools.some(tool => tool.path === "/tuning/flm"))
|
||||
assert.equal(catalog.tools.filter(tool => tool.path.startsWith('/tuning')).length, 1)
|
||||
const plots = readFileSync(new URL('../web/js/plots.js', import.meta.url), 'utf8')
|
||||
assert.doesNotMatch(plots, /href="#\/tuning\/flm"/)
|
||||
const driving = readFileSync(new URL('../web/js/driving.js', import.meta.url), 'utf8')
|
||||
assert.match(driving, /go\('\/tuning\/plots'\)/)
|
||||
assert.doesNotMatch(driving, /\/tuning\/flm/)
|
||||
assert.match(FlmPage.template, /<section v-if="!embedded"[^>]*><div class="gx-actions"><h3>Live steering response/)
|
||||
assert.doesNotMatch(FlmPage.template, /href="#\/tuning\/plots"/)
|
||||
const route = readFileSync(new URL("../web/js/route-workspace.js", import.meta.url), "utf8")
|
||||
assert.doesNotMatch(route, /Analyze driving logs/)
|
||||
|
||||
@@ -77,7 +91,17 @@ function instance() {
|
||||
}
|
||||
const originalFetch = globalThis.fetch, originalDocument = globalThis.document
|
||||
try {
|
||||
globalThis.document = { hidden: false, removeEventListener() {} }
|
||||
globalThis.document = { hidden: false, addEventListener() {}, removeEventListener() {} }
|
||||
for (const embedded of [true, false]) {
|
||||
const page = { ...FlmPage.data(), embedded, mode: 'local', unauthorized() {}, load() {} }
|
||||
let plotStarts = 0
|
||||
FlmPage.created.call(page)
|
||||
page.feed.start = () => { plotStarts++ }
|
||||
page.feed.stop = () => {}
|
||||
FlmPage.mounted.call(page)
|
||||
assert.equal(plotStarts, embedded ? 0 : 1, 'Embedded results reuse the existing live plots')
|
||||
FlmPage.beforeUnmount.call(page)
|
||||
}
|
||||
const calls = []
|
||||
globalThis.fetch = async (url, options) => { calls.push({ url, options }); return { ok: true, json: async () => value } }
|
||||
const vm = instance(); await vm.load()
|
||||
|
||||
@@ -19,7 +19,7 @@ class PreviewContractTest(unittest.TestCase):
|
||||
self.assertEqual([tool["path"] for tool in catalog["tools"] if tool["availability"] == "partial-preview"], ["/logs"])
|
||||
self.assertEqual([tool["path"] for tool in catalog["tools"] if tool["availability"] == "local-only"],
|
||||
["/bluetooth", "/cameras", "/device-preferences", "/galaxy", "/manage_models", "/model_laboratory",
|
||||
"/navigation", "/system", "/theme_maker", "/tuning/plots", "/tuning/flm", "/vehicle", "/android-auto"])
|
||||
"/navigation", "/system", "/theme_maker", "/tuning/plots", "/vehicle", "/android-auto"])
|
||||
self.assertTrue(all(tool["availability"] in {"unavailable", "partial-preview", "local-only"} for tool in catalog["tools"]))
|
||||
self.assertTrue(all(tool["name"] and tool["description"] and tool["icon"] for tool in catalog["tools"]))
|
||||
|
||||
|
||||
@@ -75,14 +75,7 @@
|
||||
"name": "Plots & Analysis",
|
||||
"path": "/tuning/plots",
|
||||
"icon": "bi-sign-turn-right",
|
||||
"description": "Live steering and acceleration plots",
|
||||
"availability": "local-only"
|
||||
},
|
||||
{
|
||||
"name": "Auto Learn Results",
|
||||
"path": "/tuning/flm",
|
||||
"icon": "bi-sliders",
|
||||
"description": "StarPilot learned steering corrections and fitting progress",
|
||||
"description": "Live control plots and StarPilot Auto Learn results",
|
||||
"availability": "local-only"
|
||||
},
|
||||
{
|
||||
@@ -96,7 +89,7 @@
|
||||
"name": "Android Auto",
|
||||
"path": "/android-auto",
|
||||
"icon": "bi-phone",
|
||||
"description": "Show StarPilot on your car’s screen",
|
||||
"description": "Show StarPilot on your car\u2019s screen",
|
||||
"availability": "local-only",
|
||||
"visibility": "authenticated"
|
||||
}
|
||||
|
||||
@@ -250,8 +250,10 @@ createApp({
|
||||
<SettingsPage @nested="ensureBackEntry" ref="activeSettings" v-else-if="drivingSettingsPage" :key="drivingSettingsPage" :mode="state.monitorMode" :unauthorized="sessionExpired" :initial-page="drivingSettingsPage" :title="'Driving settings · ' + drivingSettingsPage.replaceAll('_', ' ')" :return-to="returnToDriving" />
|
||||
<SettingsPage @nested="ensureBackEntry" ref="activeSettings" v-else-if="route.path === '/settings'" :key="state.searchPage || 'hub'" :mode="state.monitorMode" :unauthorized="sessionExpired"
|
||||
:initial-page="state.searchPage || 'hub'" :return-to="state.searchPage ? returnFromSearch : null" />
|
||||
<PlotsPage v-else-if="['/tuning', '/tuning/plots'].includes(route.path)" :mode="state.monitorMode" :unauthorized="sessionExpired" />
|
||||
<FlmPage v-else-if="route.path === '/tuning/flm'" :mode="state.monitorMode" :unauthorized="sessionExpired" :go="go" />
|
||||
<div v-else-if="['/tuning', '/tuning/plots', '/tuning/flm'].includes(route.path)" class="gx-stack">
|
||||
<PlotsPage :mode="state.monitorMode" :unauthorized="sessionExpired" />
|
||||
<FlmPage embedded :mode="state.monitorMode" :unauthorized="sessionExpired" />
|
||||
</div>
|
||||
<GxState v-else :title="pageName">This capability is unavailable in this build. No operation was attempted.</GxState>
|
||||
</div></Transition></div></main>
|
||||
<BottomNav :path="route.path" :local-access="authState.localAccess" @navigate="go" />
|
||||
|
||||
@@ -51,7 +51,7 @@ export const DrivingPage = {
|
||||
</section>
|
||||
<div class="gx-driving__extras gx-actions"><button type="button" class="gx-btn gx-btn--tonal" @click="go('/driving/longitudinal-curves')">Edit longitudinal curves</button>
|
||||
<button type="button" class="gx-btn gx-btn--tonal" @click="go('/tuning/plots')">Live plots</button>
|
||||
<button type="button" class="gx-btn gx-btn--tonal" @click="go('/tuning/flm')">Auto Learn results</button></div>
|
||||
<button type="button" class="gx-btn gx-btn--tonal" @click="go('/tuning/plots')">Auto Learn results</button></div>
|
||||
</template>
|
||||
</section>`,
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@ import { validTrainingResults, trainingSummary, readable, detailText, surfaceRow
|
||||
export const FlmPage = {
|
||||
name: "FlmPage",
|
||||
components: { GxState, GxNotice, PlotGraph },
|
||||
props: { mode: { type: String, required: true }, unauthorized: { type: Function, required: true } },
|
||||
props: { mode: { type: String, required: true }, unauthorized: { type: Function, required: true }, embedded: { type: Boolean, default: false } },
|
||||
data: () => ({ report: null, error: "", loading: false, request: null, receivedAt: 0, now: 0,
|
||||
status: "idle", data: null, history: [], plotError: "", showPlots: true }),
|
||||
created() {
|
||||
if (this.embedded) this.showPlots = false
|
||||
this.feed = new PlotsFeed({ publish: update => {
|
||||
const { error, ...values } = update
|
||||
Object.assign(this.$data, values, { plotError: error })
|
||||
@@ -26,7 +27,7 @@ export const FlmPage = {
|
||||
else { this.load(); if (this.showPlots && this.mode === "local") this.feed.start() }
|
||||
}
|
||||
document.addEventListener("visibilitychange", this.visibility)
|
||||
if (this.mode === "local") { this.poller.start(); this.load(); if (!document.hidden) this.feed.start() }
|
||||
if (this.mode === "local") { this.poller.start(); this.load(); if (this.showPlots && !document.hidden) this.feed.start() }
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.activePage = false; clearTimeout(this.freshnessTimer); this.poller?.stop(); this.request?.abort(); this.request = null; this.feed.stop()
|
||||
@@ -79,9 +80,9 @@ export const FlmPage = {
|
||||
},
|
||||
},
|
||||
template: `<div class="gx-view gx-flm gx-stack">
|
||||
<header class="gx-settings__header gx-page-header"><div><h2>StarPilot Auto Learn</h2>
|
||||
<header class="gx-settings__header gx-page-header"><div><h3 v-if="embedded">StarPilot Auto Learn</h3><h2 v-else>StarPilot Auto Learn</h2>
|
||||
<p>Learned steering corrections, collection progress and current application.</p></div>
|
||||
<a class="gx-btn gx-btn--tonal" href="#/tuning/plots">Live plots</a></header>
|
||||
</header>
|
||||
<GxState v-if="mode !== 'local'">Auto Learn results require authenticated local Galaxy access.</GxState>
|
||||
<template v-else>
|
||||
<section class="gx-card gx-flm__panel"><h3 role="status">{{ summary }}</h3>
|
||||
@@ -118,10 +119,10 @@ export const FlmPage = {
|
||||
<section class="gx-card gx-flm__panel"><h3>Latest fit comparison</h3>
|
||||
<dl v-if="losses"><dt>Baseline loss</dt><dd>{{ losses.baseline.toPrecision(5) }}</dd><dt>Learned proposal loss</dt><dd>{{ losses.learned.toPrecision(5) }}</dd></dl>
|
||||
<p v-else>No fit comparison has been reported yet.</p>
|
||||
<p class="gx-note">These are fitter-reported losses for the latest proposal. Live tracking below is a separate observation and does not measure a before/after learning benefit.</p>
|
||||
<p class="gx-note">These are fitter-reported losses for the latest proposal. The live control plots are a separate observation and does not measure a before/after learning benefit.</p>
|
||||
</section>
|
||||
</div>
|
||||
<section class="gx-card gx-flm__panel"><div class="gx-actions"><h3>Live steering response</h3>
|
||||
<section v-if="!embedded" class="gx-card gx-flm__panel"><div class="gx-actions"><h3>Live steering response</h3>
|
||||
<button type="button" class="gx-btn gx-btn--tonal" :aria-expanded="showPlots" @click="togglePlots">{{ showPlots ? 'Pause live plots' : 'Show live plots' }}</button></div>
|
||||
<template v-if="showPlots"><p role="status">{{ status === 'current' ? 'Live' : status === 'stale' ? 'Last received window — reconnecting' : 'Waiting for steering telemetry' }}</p>
|
||||
<p v-if="plotError" class="gx-note">{{ plotError }}</p>
|
||||
|
||||
@@ -223,8 +223,7 @@ export const PlotsPage = {
|
||||
},
|
||||
template: `
|
||||
<div class="gx-view gx-plots">
|
||||
<header class="gx-settings__header gx-page-header"><div><h2>Plots</h2><p>Live local control observations. Graphs and match scores are diagnostic, not driving-control checks.</p></div>
|
||||
<div class="gx-actions"><a class="gx-btn gx-btn--tonal" href="#/tuning/flm">Auto Learn results</a></div></header>
|
||||
<header class="gx-settings__header gx-page-header"><div><h2>Plots & Analysis</h2><p>Live local control observations. Graphs and match scores are diagnostic, not driving-control checks.</p></div></header>
|
||||
<GxState v-if="mode !== 'local'">Live plots require the authenticated local Galaxy service.</GxState>
|
||||
<template v-else>
|
||||
<div class="gx-plots__toolbar"><GxIconButton :label="paused ? 'Resume live plots' : 'Pause live plots'" :icon="paused ? 'bi-play-fill' : 'bi-pause-fill'" @click="togglePause" />
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"protected_inventory_sha256": "7af93a9b9dfe7c8fbba91f94cd2567216d327acf626051bc61d23656c1a3dcb3", "schema_version": 1}
|
||||
{"protected_inventory_sha256": "64d12e6db0a074393432aa9f80dc6c251227f216e1478f5075e077fd1ce2d6ee", "schema_version": 1}
|
||||
|
||||
Reference in New Issue
Block a user