mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-06 00:36:25 +08:00
ui(galaxy): add guided E2E override slider
Add a 0.05-step range control with plain-language guidance for lane-centering E2E authority. Descriptions group the same behavior across five practical levels, from rigid centering to immediate hazard response. Co-authored-by: jc01rho <4989674+jc01rho@users.noreply.github.com>
This commit is contained in:
@@ -48,3 +48,5 @@ def test_lane_centering_galaxy_controls():
|
||||
assert e2e_authority["min"] == 0.0
|
||||
assert e2e_authority["max"] == 1.0
|
||||
assert e2e_authority["step"] == 0.05
|
||||
assert e2e_authority["control"] == "slider"
|
||||
assert len(e2e_authority["description_steps"]) == 5
|
||||
|
||||
@@ -515,6 +515,35 @@
|
||||
opacity: var(--disabled-opacity);
|
||||
}
|
||||
|
||||
.ds-slider-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.ds-slider-container {
|
||||
max-width: 420px;
|
||||
}
|
||||
}
|
||||
|
||||
.ds-slider {
|
||||
accent-color: var(--main-fg);
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ds-slider:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: var(--disabled-opacity);
|
||||
}
|
||||
|
||||
.ds-slider-scale {
|
||||
color: var(--text-muted);
|
||||
display: flex;
|
||||
font-size: var(--font-size-xs);
|
||||
justify-content: space-between;
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
|
||||
/* ――― Favorite Slots ――― */
|
||||
.ds-favorites-panel {
|
||||
display: grid;
|
||||
|
||||
@@ -73,6 +73,7 @@ const state = reactive({
|
||||
fetched: false,
|
||||
activeSectionSlug: "",
|
||||
numericUpdating: {},
|
||||
sliderPreviewValues: {},
|
||||
actionUpdating: {},
|
||||
favoriteLoading: false,
|
||||
favoriteSaving: false,
|
||||
@@ -840,6 +841,16 @@ function getParamDisplayLabel(key) {
|
||||
return state.paramMetaByKey[key]?.label || key
|
||||
}
|
||||
|
||||
function getSliderDescription(param, value) {
|
||||
const steps = Array.isArray(param.description_steps) ? param.description_steps : []
|
||||
if (!steps.length) return param.description || ""
|
||||
|
||||
const numericValue = Number(value)
|
||||
if (!Number.isFinite(numericValue)) return param.description || ""
|
||||
const selected = steps.find(step => numericValue <= Number(step.max)) || steps[steps.length - 1]
|
||||
return selected.description || param.description || ""
|
||||
}
|
||||
|
||||
function confirmPandaFirmwareToggle(key, enabled) {
|
||||
if (!PANDA_FIRMWARE_TOGGLE_KEYS.has(key)) return true
|
||||
|
||||
@@ -870,7 +881,12 @@ function syncNumericDisplay(param, rawValue) {
|
||||
|
||||
async function updateNumericParam(param, numericValue, options = {}) {
|
||||
const key = param.key
|
||||
const current = state.values[key]
|
||||
const current = options.previousValue !== undefined ? options.previousValue : state.values[key]
|
||||
if (Object.prototype.hasOwnProperty.call(state.sliderPreviewValues, key)) {
|
||||
const nextPreviewValues = { ...state.sliderPreviewValues }
|
||||
delete nextPreviewValues[key]
|
||||
state.sliderPreviewValues = nextPreviewValues
|
||||
}
|
||||
const successMessage = options.successMessage
|
||||
state.numericUpdating = { ...state.numericUpdating, [key]: true }
|
||||
state.values = { ...state.values, [key]: numericValue }
|
||||
@@ -905,6 +921,40 @@ async function updateNumericParam(param, numericValue, options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function previewSliderParam(param, rawValue) {
|
||||
if (isNumericUpdating(param.key)) return
|
||||
|
||||
const bounds = numericBounds(param)
|
||||
const precision = stepPrecision(bounds.step, param.precision)
|
||||
const snapped = snapNumericToBoundsAndStep(rawValue, bounds, precision)
|
||||
if (snapped === null) return
|
||||
|
||||
state.sliderPreviewValues = { ...state.sliderPreviewValues, [param.key]: snapped }
|
||||
syncNumericDisplay(param, snapped)
|
||||
}
|
||||
|
||||
function commitSliderParam(param, rawValue) {
|
||||
if (isNumericUpdating(param.key)) return
|
||||
|
||||
const bounds = numericBounds(param)
|
||||
const precision = stepPrecision(bounds.step, param.precision)
|
||||
const next = snapNumericToBoundsAndStep(rawValue, bounds, precision)
|
||||
if (next === null) return
|
||||
|
||||
const current = resolveCurrentNumericValue(param, bounds)
|
||||
const previewValues = { ...state.sliderPreviewValues }
|
||||
delete previewValues[param.key]
|
||||
state.sliderPreviewValues = previewValues
|
||||
|
||||
const epsilon = Math.pow(10, -(precision + 2))
|
||||
if (Math.abs(next - current) <= epsilon) {
|
||||
syncNumericDisplay(param, current)
|
||||
return
|
||||
}
|
||||
|
||||
updateNumericParam(param, next, { previousValue: current })
|
||||
}
|
||||
|
||||
function stepNumericParam(param, direction) {
|
||||
const bounds = numericBounds(param)
|
||||
const min = Number(bounds.min)
|
||||
@@ -1408,6 +1458,7 @@ function renderSettingRow(p) {
|
||||
}
|
||||
|
||||
const isNumeric = p.ui_type === "numeric"
|
||||
const isSlider = isNumeric && p.control === "slider"
|
||||
const isColor = p.ui_type === "color"
|
||||
const isAction = p.ui_type === "action"
|
||||
const isGroup = isGroupParam(p)
|
||||
@@ -1427,6 +1478,41 @@ function renderSettingRow(p) {
|
||||
${() => state.actionUpdating[p.key] ? "Resetting..." : (p.action_label || "Run")}
|
||||
</button>
|
||||
`
|
||||
} else if (isSlider) {
|
||||
rowControl = html`
|
||||
<div class="ds-slider-container">
|
||||
<input
|
||||
type="range"
|
||||
class="ds-slider"
|
||||
min="${numericBounds(p).min}"
|
||||
max="${numericBounds(p).max}"
|
||||
step="${numericBounds(p).step}"
|
||||
aria-label="${p.label}"
|
||||
disabled="${() => isLocked() || isNumericUpdating(p.key)}"
|
||||
value="${() => {
|
||||
const bounds = numericBounds(p)
|
||||
const preview = state.sliderPreviewValues[p.key]
|
||||
return formatNumericForInput(preview ?? resolveCurrentNumericValue(p, bounds), stepPrecision(bounds.step, p.precision))
|
||||
}}"
|
||||
@input="${(event) => previewSliderParam(p, event.currentTarget.value)}"
|
||||
@change="${(event) => commitSliderParam(p, event.currentTarget.value)}" />
|
||||
<div class="ds-slider-scale">
|
||||
<span>${formatSliderValue(numericBounds(p).min, String(numericBounds(p).step), p.precision, p.key)}</span>
|
||||
<span>${formatSliderValue(numericBounds(p).max, String(numericBounds(p).step), p.precision, p.key)}</span>
|
||||
</div>
|
||||
<button
|
||||
class="ds-reset-btn"
|
||||
disabled="${() => {
|
||||
const bounds = numericBounds(p)
|
||||
const defaultValue = resolveDefaultNumericValue(p, bounds)
|
||||
const currentValue = resolveCurrentNumericValue(p, bounds)
|
||||
const precision = stepPrecision(bounds.step, p.precision)
|
||||
const epsilon = Math.pow(10, -(precision + 2))
|
||||
return isLocked() || isNumericUpdating(p.key) || defaultValue === null || Math.abs(defaultValue - currentValue) <= epsilon
|
||||
}}"
|
||||
@click="${() => resetNumericParam(p)}">Reset to Default</button>
|
||||
</div>
|
||||
`
|
||||
} else if (isNumeric) {
|
||||
rowControl = html`
|
||||
<div class="ds-stepper-container">
|
||||
@@ -1550,7 +1636,9 @@ function renderSettingRow(p) {
|
||||
<span class="ds-row-label">${p.label}</span>
|
||||
${flmParamStatus ? html`<span class="ds-flm-badge">Currently overridden by FLM</span>` : ""}
|
||||
</div>
|
||||
${p.description ? html`<div class="ds-row-desc">${p.description}</div>` : ""}
|
||||
${p.description_steps
|
||||
? html`<div class="ds-row-desc">${() => getSliderDescription(p, state.sliderPreviewValues[p.key] ?? state.values[p.key])}</div>`
|
||||
: (p.description ? html`<div class="ds-row-desc">${p.description}</div>` : "")}
|
||||
${() => {
|
||||
const reason = lockReason()
|
||||
return reason ? html`<div class="ds-row-desc"><strong>Locked:</strong> ${reason}</div>` : ""
|
||||
@@ -1584,7 +1672,7 @@ function renderSettingRow(p) {
|
||||
</div>
|
||||
${(isNumeric || isColor) ? html`<span class="ds-row-value" id="ds-display-${p.key}">${() => {
|
||||
if (isColor) return formatColorDisplayValue(p)
|
||||
const currentValue = state.values[p.key]
|
||||
const currentValue = state.sliderPreviewValues[p.key] ?? state.values[p.key]
|
||||
const bounds = numericBounds(p)
|
||||
return currentValue !== undefined ? formatSliderValue(currentValue, String(bounds.step), p.precision, p.key) : ".."
|
||||
}}</span>` : ""}
|
||||
|
||||
@@ -4108,15 +4108,38 @@
|
||||
{
|
||||
"key": "LaneCenteringE2EAuthority",
|
||||
"label": "E2E Override Strength",
|
||||
"description": "How strongly a confident end-to-end path can override lane centering when it deliberately departs the lane target. 1.0 gives the model full authority; 0.0 disables break-in.",
|
||||
"description": "Choose how strongly the vision model may override lane centering when it sees a hazard.",
|
||||
"data_type": "float",
|
||||
"ui_type": "numeric",
|
||||
"control": "slider",
|
||||
"min": 0.0,
|
||||
"max": 1.0,
|
||||
"step": 0.05,
|
||||
"precision": 2,
|
||||
"parent_key": "LaneCentering",
|
||||
"settings_tier": "advanced"
|
||||
"settings_tier": "advanced",
|
||||
"description_steps": [
|
||||
{
|
||||
"max": 0.2,
|
||||
"description": "Vehicle rigidly tries to center itself rather than avoiding hazards, potholes, or road debris."
|
||||
},
|
||||
{
|
||||
"max": 0.4,
|
||||
"description": "Vehicle tries to center itself and slowly adjusts away from hazards, but may react too slowly."
|
||||
},
|
||||
{
|
||||
"max": 0.6,
|
||||
"description": "Vehicle provides additional space around hazards like cyclists and vehicles crossing into the lane."
|
||||
},
|
||||
{
|
||||
"max": 0.8,
|
||||
"description": "Vehicle smooths out hazard avoidance, making less rapid adjustments."
|
||||
},
|
||||
{
|
||||
"max": 1.0,
|
||||
"description": "Vehicle reacts to hazards recognized by the vision model as quickly as possible."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "HondaLateralPidKpScale",
|
||||
|
||||
Reference in New Issue
Block a user