mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-06-27 17:42:04 +08:00
plexy and noah
This commit is contained in:
@@ -632,29 +632,13 @@ KIA_EV6_LOW_SPEED_CENTER_TAPER_SPEED_MAX = 8.5
|
||||
KIA_EV6_LOW_SPEED_CENTER_TAPER_SPEED_WIDTH = 1.4
|
||||
|
||||
VOLT_PLEXY_LATERAL_TESTING_GROUND_ID = testing_ground.id_7
|
||||
VOLT_PLEXY_FF_GAIN_LEFT = 0.12
|
||||
VOLT_PLEXY_FF_GAIN_RIGHT = 0.07
|
||||
VOLT_PLEXY_FF_ONSET = 0.10
|
||||
VOLT_PLEXY_FF_ONSET_WIDTH = 0.06
|
||||
VOLT_PLEXY_FF_CUTOFF = 1.35
|
||||
VOLT_PLEXY_FF_CUTOFF_WIDTH = 0.24
|
||||
VOLT_PLEXY_TRANSITION_SPEED = 10.5
|
||||
VOLT_PLEXY_PHASE_SCALE = 0.10
|
||||
VOLT_PLEXY_TURN_IN_BOOST_LEFT = 0.22
|
||||
VOLT_PLEXY_TURN_IN_BOOST_RIGHT = 0.24
|
||||
VOLT_PLEXY_UNWIND_TAPER_LEFT = 0.22
|
||||
VOLT_PLEXY_UNWIND_TAPER_RIGHT = 0.56
|
||||
VOLT_PLEXY_FRICTION_MULT = 1.04
|
||||
VOLT_PLEXY_FRICTION_LAT_RISE = 0.22
|
||||
VOLT_PLEXY_FRICTION_JERK_RISE = 0.24
|
||||
VOLT_PLEXY_TURN_IN_THRESHOLD_REDUCTION_LEFT = 0.16
|
||||
VOLT_PLEXY_TURN_IN_THRESHOLD_REDUCTION_RIGHT = 0.12
|
||||
VOLT_PLEXY_UNWIND_THRESHOLD_INCREASE_LEFT = 0.15
|
||||
VOLT_PLEXY_UNWIND_THRESHOLD_INCREASE_RIGHT = 0.34
|
||||
VOLT_PLEXY_TURN_IN_FRICTION_BOOST_LEFT = 0.08
|
||||
VOLT_PLEXY_TURN_IN_FRICTION_BOOST_RIGHT = 0.06
|
||||
VOLT_PLEXY_UNWIND_FRICTION_REDUCTION_LEFT = 0.16
|
||||
VOLT_PLEXY_UNWIND_FRICTION_REDUCTION_RIGHT = 0.40
|
||||
VOLT_PLEXY_FF_EXTRA_MULT_LEFT = 1.10
|
||||
VOLT_PLEXY_FF_EXTRA_MULT_RIGHT = 1.22
|
||||
VOLT_PLEXY_FRICTION_SCALE_MULT_LEFT = 1.04
|
||||
VOLT_PLEXY_FRICTION_SCALE_MULT_RIGHT = 1.08
|
||||
VOLT_PLEXY_FRICTION_THRESHOLD_MULT_LEFT = 0.95
|
||||
VOLT_PLEXY_FRICTION_THRESHOLD_MULT_RIGHT = 0.93
|
||||
VOLT_PLEXY_CENTER_TAPER_REDUCTION_MULT = 0.80
|
||||
PRIUS_TRANSITION_SPEED = 10.0
|
||||
PRIUS_PHASE_SCALE = 0.09
|
||||
PRIUS_FF_GAIN_LEFT = 0.12
|
||||
@@ -2023,72 +2007,34 @@ def volt_plexy_lateral_testing_ground_active() -> bool:
|
||||
return testing_ground.use(VOLT_PLEXY_LATERAL_TESTING_GROUND_ID)
|
||||
|
||||
|
||||
def _volt_plexy_sigmoid(x: float) -> float:
|
||||
return _sigmoid(x)
|
||||
|
||||
|
||||
def _volt_plexy_low_speed_factor(v_ego: float) -> float:
|
||||
return 1.0 / (1.0 + (max(v_ego, 0.0) / VOLT_PLEXY_TRANSITION_SPEED) ** 2)
|
||||
|
||||
|
||||
def _volt_plexy_transition_phase(desired_lateral_accel: float, desired_lateral_jerk: float) -> float:
|
||||
return math.tanh((desired_lateral_accel * desired_lateral_jerk) / VOLT_PLEXY_PHASE_SCALE)
|
||||
|
||||
|
||||
def _volt_plexy_side_value(desired_lateral_accel: float, left_value: float, right_value: float) -> float:
|
||||
return left_value if desired_lateral_accel >= 0.0 else right_value
|
||||
|
||||
|
||||
def _volt_plexy_transition_envelope(v_ego: float, desired_lateral_accel: float, desired_lateral_jerk: float) -> float:
|
||||
lat_factor = 1.0 - math.exp(-abs(desired_lateral_accel) / VOLT_PLEXY_FRICTION_LAT_RISE)
|
||||
jerk_factor = 1.0 - math.exp(-abs(desired_lateral_jerk) / VOLT_PLEXY_FRICTION_JERK_RISE)
|
||||
return _volt_plexy_low_speed_factor(v_ego) * lat_factor * jerk_factor
|
||||
|
||||
|
||||
def get_volt_plexy_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: float, v_ego: float) -> float:
|
||||
if desired_lateral_accel == 0.0:
|
||||
return 1.0
|
||||
|
||||
gain = _volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_FF_GAIN_LEFT, VOLT_PLEXY_FF_GAIN_RIGHT)
|
||||
abs_lateral_accel = abs(desired_lateral_accel)
|
||||
onset = _volt_plexy_sigmoid((abs_lateral_accel - VOLT_PLEXY_FF_ONSET) / VOLT_PLEXY_FF_ONSET_WIDTH)
|
||||
cutoff = _volt_plexy_sigmoid((VOLT_PLEXY_FF_CUTOFF - abs_lateral_accel) / VOLT_PLEXY_FF_CUTOFF_WIDTH)
|
||||
extra_scale = gain * onset * cutoff
|
||||
phase = _volt_plexy_transition_phase(desired_lateral_accel, desired_lateral_jerk)
|
||||
turn_in_weight = max(phase, 0.0)
|
||||
unwind_weight = max(-phase, 0.0)
|
||||
low_speed_factor = _volt_plexy_low_speed_factor(v_ego)
|
||||
turn_in_boost = 1.0 + (_volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_TURN_IN_BOOST_LEFT, VOLT_PLEXY_TURN_IN_BOOST_RIGHT) *
|
||||
turn_in_weight * low_speed_factor)
|
||||
unwind_taper = 1.0 - (_volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_UNWIND_TAPER_LEFT, VOLT_PLEXY_UNWIND_TAPER_RIGHT) *
|
||||
unwind_weight * (0.35 + 0.65 * low_speed_factor))
|
||||
return 1.0 + (extra_scale * turn_in_boost * max(unwind_taper, 0.0))
|
||||
standard_scale = get_volt_standard_ff_scale(desired_lateral_accel, desired_lateral_jerk, v_ego)
|
||||
extra_scale = standard_scale - 1.0
|
||||
extra_mult = _volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_FF_EXTRA_MULT_LEFT, VOLT_PLEXY_FF_EXTRA_MULT_RIGHT)
|
||||
return 1.0 + (extra_scale * extra_mult)
|
||||
|
||||
|
||||
def get_volt_plexy_friction_threshold(v_ego: float, desired_lateral_accel: float = 0.0, desired_lateral_jerk: float = 0.0) -> float:
|
||||
base_threshold = get_friction_threshold(v_ego)
|
||||
transition_envelope = _volt_plexy_transition_envelope(v_ego, desired_lateral_accel, desired_lateral_jerk)
|
||||
phase = _volt_plexy_transition_phase(desired_lateral_accel, desired_lateral_jerk)
|
||||
turn_in_weight = max(phase, 0.0)
|
||||
unwind_weight = max(-phase, 0.0)
|
||||
threshold_scale = 1.0 - (_volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_TURN_IN_THRESHOLD_REDUCTION_LEFT, VOLT_PLEXY_TURN_IN_THRESHOLD_REDUCTION_RIGHT) *
|
||||
transition_envelope * turn_in_weight)
|
||||
threshold_scale += (_volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_UNWIND_THRESHOLD_INCREASE_LEFT, VOLT_PLEXY_UNWIND_THRESHOLD_INCREASE_RIGHT) *
|
||||
transition_envelope * unwind_weight)
|
||||
return base_threshold * min(max(threshold_scale, 0.84), 1.14)
|
||||
standard_threshold = get_volt_standard_friction_threshold(v_ego, desired_lateral_accel, desired_lateral_jerk)
|
||||
threshold_mult = _volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_FRICTION_THRESHOLD_MULT_LEFT, VOLT_PLEXY_FRICTION_THRESHOLD_MULT_RIGHT)
|
||||
return standard_threshold * threshold_mult
|
||||
|
||||
|
||||
def get_volt_plexy_friction_scale(v_ego: float, desired_lateral_accel: float, desired_lateral_jerk: float) -> float:
|
||||
transition_envelope = _volt_plexy_transition_envelope(v_ego, desired_lateral_accel, desired_lateral_jerk)
|
||||
phase = _volt_plexy_transition_phase(desired_lateral_accel, desired_lateral_jerk)
|
||||
turn_in_weight = max(phase, 0.0)
|
||||
unwind_weight = max(-phase, 0.0)
|
||||
friction_scale = VOLT_PLEXY_FRICTION_MULT
|
||||
friction_scale += (_volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_TURN_IN_FRICTION_BOOST_LEFT, VOLT_PLEXY_TURN_IN_FRICTION_BOOST_RIGHT) *
|
||||
transition_envelope * turn_in_weight)
|
||||
friction_scale -= (_volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_UNWIND_FRICTION_REDUCTION_LEFT, VOLT_PLEXY_UNWIND_FRICTION_REDUCTION_RIGHT) *
|
||||
transition_envelope * unwind_weight)
|
||||
return min(max(friction_scale, 0.90), 1.12)
|
||||
standard_scale = get_volt_standard_friction_scale(v_ego, desired_lateral_accel, desired_lateral_jerk)
|
||||
friction_extra = standard_scale - 1.0
|
||||
friction_mult = _volt_plexy_side_value(desired_lateral_accel, VOLT_PLEXY_FRICTION_SCALE_MULT_LEFT, VOLT_PLEXY_FRICTION_SCALE_MULT_RIGHT)
|
||||
return 1.0 + (friction_extra * friction_mult)
|
||||
|
||||
|
||||
def get_volt_plexy_center_taper_scale(desired_lateral_accel: float, v_ego: float) -> float:
|
||||
standard_scale = get_volt_standard_center_taper_scale(desired_lateral_accel, v_ego)
|
||||
standard_reduction = 1.0 - standard_scale
|
||||
return 1.0 - (standard_reduction * VOLT_PLEXY_CENTER_TAPER_REDUCTION_MULT)
|
||||
|
||||
|
||||
class LatControlTorque(LatControl):
|
||||
@@ -2131,7 +2077,6 @@ class LatControlTorque(LatControl):
|
||||
self.is_kia_forte = CP.carFingerprint in KIA_FORTE_CARS
|
||||
self.is_kia_ev6 = CP.carFingerprint in KIA_EV6_CARS
|
||||
self.is_civic_bosch_modified = CP.carFingerprint == HONDA_CAR.HONDA_CIVIC_BOSCH and bool(CP.flags & HondaFlags.EPS_MODIFIED)
|
||||
self.is_volt_cc = CP.carFingerprint == GM_CAR.CHEVROLET_VOLT_CC
|
||||
self.is_silverado = CP.carFingerprint in SILVERADO_CARS
|
||||
if self.is_ioniq_6:
|
||||
self.low_speed_reset_threshold = min(self.low_speed_reset_threshold, IONIQ_6_LOW_SPEED_PID_RESET_SPEED)
|
||||
@@ -2268,9 +2213,10 @@ class LatControlTorque(LatControl):
|
||||
kia_niro_phev_2022_active = self.is_kia_niro_phev_2022
|
||||
kia_forte_active = self.is_kia_forte
|
||||
kia_ev6_test_active = self.is_kia_ev6 and kia_ev6_lateral_testing_ground_active()
|
||||
volt_plexy_test_active = self.is_volt_cc and volt_plexy_lateral_testing_ground_active()
|
||||
volt_plexy_test_active = self.is_volt_standard and volt_plexy_lateral_testing_ground_active()
|
||||
ioniq_5_center_taper = get_ioniq_5_center_taper_scale(setpoint, CS.vEgo) if ioniq_5_active else 1.0
|
||||
volt_standard_center_taper = get_volt_standard_center_taper_scale(setpoint, CS.vEgo) if volt_standard_test_active else 1.0
|
||||
volt_plexy_center_taper = get_volt_plexy_center_taper_scale(setpoint, CS.vEgo) if volt_plexy_test_active else 1.0
|
||||
ioniq_ev_old_center_taper = get_ioniq_ev_old_center_taper_scale(setpoint, CS.vEgo) if ioniq_ev_old_active else 1.0
|
||||
ioniq_6_center_taper = get_ioniq_6_center_taper_scale(setpoint, CS.vEgo) if ioniq_6_active else 1.0
|
||||
sonata_center_taper = get_sonata_center_taper_scale(setpoint, CS.vEgo) if sonata_active else 1.0
|
||||
@@ -2344,9 +2290,10 @@ class LatControlTorque(LatControl):
|
||||
elif self.is_silverado:
|
||||
ff *= silverado_center_taper
|
||||
elif volt_plexy_test_active:
|
||||
ff *= get_volt_plexy_ff_scale(setpoint, desired_lateral_jerk, CS.vEgo)
|
||||
ff *= get_volt_plexy_ff_scale(setpoint, desired_lateral_jerk, CS.vEgo) * volt_plexy_center_taper
|
||||
friction_threshold = get_volt_plexy_friction_threshold(CS.vEgo, setpoint, desired_lateral_jerk)
|
||||
friction_scale = get_volt_plexy_friction_scale(CS.vEgo, setpoint, desired_lateral_jerk)
|
||||
friction_scale = 1.0 + ((friction_scale - 1.0) * volt_plexy_center_taper)
|
||||
elif self.is_civic_bosch_modified:
|
||||
ff *= get_civic_bosch_modified_b_ff_scale(setpoint, desired_lateral_jerk, CS.vEgo) * civic_bosch_modified_a_center_taper
|
||||
friction_threshold = CIVIC_BOSCH_MODIFIED_B_FIXED_FRICTION_THRESHOLD
|
||||
@@ -2379,6 +2326,8 @@ class LatControlTorque(LatControl):
|
||||
output_torque, CS.vEgo)
|
||||
elif volt_standard_test_active:
|
||||
output_torque *= volt_standard_center_taper
|
||||
elif volt_plexy_test_active:
|
||||
output_torque *= volt_plexy_center_taper
|
||||
elif kia_ev6_test_active:
|
||||
output_torque *= kia_ev6_low_speed_center_taper
|
||||
elif self.is_silverado:
|
||||
|
||||
@@ -207,19 +207,42 @@
|
||||
color: var(--warning-bg);
|
||||
}
|
||||
|
||||
.mm-chip-user-favorite {
|
||||
background: rgba(142, 108, 211, 0.18);
|
||||
border-color: rgba(142, 108, 211, 0.55);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.mm-chip-warning {
|
||||
background: rgba(224, 85, 119, 0.12);
|
||||
border-color: rgba(224, 85, 119, 0.45);
|
||||
color: var(--danger-fg);
|
||||
}
|
||||
|
||||
.mm-icon-btn,
|
||||
.mm-star {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #e0b45a;
|
||||
border: 1px solid var(--sidebar-border-color);
|
||||
border-radius: var(--border-radius-base);
|
||||
color: var(--text-muted);
|
||||
display: inline-flex;
|
||||
font-size: 1rem;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
padding: 0;
|
||||
min-height: 2.2rem;
|
||||
min-width: 2.45rem;
|
||||
padding: 0.45rem;
|
||||
}
|
||||
|
||||
.mm-icon-btn:hover:not(:disabled) {
|
||||
background: var(--secondary-bg);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.mm-icon-btn.is-active {
|
||||
background: rgba(142, 108, 211, 0.18);
|
||||
border-color: rgba(142, 108, 211, 0.55);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.mm-empty {
|
||||
@@ -238,7 +261,12 @@
|
||||
}
|
||||
|
||||
.mm-btn {
|
||||
width: 100%;
|
||||
flex: 1 1 auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.mm-icon-btn {
|
||||
flex: 0 0 2.6rem;
|
||||
}
|
||||
|
||||
.mm-search {
|
||||
|
||||
@@ -7,6 +7,7 @@ const state = reactive({
|
||||
actionBusy: false,
|
||||
sortMode: "alphabetical",
|
||||
communityFavoriteFilter: "all",
|
||||
userFavoriteFilter: "all",
|
||||
models: [],
|
||||
currentModel: "",
|
||||
summary: { installed: 0, missing: 0, total: 0 },
|
||||
@@ -91,6 +92,12 @@ function modelSortCompare(a, b) {
|
||||
function getFilteredModels() {
|
||||
let rows = [...state.models].filter(model => model && typeof model === "object");
|
||||
|
||||
if (state.userFavoriteFilter === "yes") {
|
||||
rows = rows.filter(model => !!model.userFavorite);
|
||||
} else if (state.userFavoriteFilter === "no") {
|
||||
rows = rows.filter(model => !model.userFavorite);
|
||||
}
|
||||
|
||||
if (state.communityFavoriteFilter === "yes") {
|
||||
rows = rows.filter(model => !!model.communityFavorite);
|
||||
} else if (state.communityFavoriteFilter === "no") {
|
||||
@@ -143,8 +150,15 @@ function getReleaseOrderedModels() {
|
||||
}
|
||||
|
||||
function getInstalledModels() {
|
||||
const rows = state.sortMode === "release_date" ? getReleaseOrderedModels() : getVisibleModels();
|
||||
return rows.filter(model => !!model.installed);
|
||||
return state.models
|
||||
.filter(model => model && typeof model === "object" && !!model.installed)
|
||||
.sort(modelSortCompare);
|
||||
}
|
||||
|
||||
function getUserFavoriteModels(installedOnly = false) {
|
||||
const rows = state.models.filter(model => model && typeof model === "object" && !!model.userFavorite);
|
||||
const filtered = installedOnly ? rows.filter(model => !!model.installed) : rows;
|
||||
return filtered.sort(modelSortCompare);
|
||||
}
|
||||
|
||||
function getCurrentModelName() {
|
||||
@@ -317,6 +331,28 @@ async function deleteModel(modelKey) {
|
||||
notify(payload.message || `Deleted files for "${modelKey}".`);
|
||||
}
|
||||
|
||||
async function setUserFavorite(modelKey, shouldFavorite) {
|
||||
const key = safeText(modelKey, "");
|
||||
if (!key) return;
|
||||
|
||||
const favorites = getUserFavoriteModels(false)
|
||||
.map(model => safeText(model.value, ""))
|
||||
.filter(Boolean)
|
||||
.filter(value => value !== key);
|
||||
|
||||
if (shouldFavorite) {
|
||||
favorites.push(key);
|
||||
}
|
||||
|
||||
const payload = await fetchJson("/api/models/preferences", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ userFavorites: favorites }),
|
||||
});
|
||||
|
||||
notify(payload.message || (shouldFavorite ? "Model added to your favorites." : "Model removed from your favorites."));
|
||||
}
|
||||
|
||||
async function refreshManifest() {
|
||||
const payload = await fetchJson("/api/models/refresh_manifest", { method: "POST" });
|
||||
notify(payload.message || "Model manifest refreshed.");
|
||||
@@ -336,7 +372,8 @@ async function runAction(action, modelKey = "") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.status.isOnroad && action !== "refresh") {
|
||||
const allowedOnroadActions = new Set(["refresh", "favorite", "unfavorite"]);
|
||||
if (state.status.isOnroad && !allowedOnroadActions.has(action)) {
|
||||
notify("Actions are blocked while onroad.", "error");
|
||||
return;
|
||||
}
|
||||
@@ -356,6 +393,12 @@ async function runAction(action, modelKey = "") {
|
||||
const confirmed = window.confirm(`Delete local files for model \"${modelKey}\"?`);
|
||||
if (!confirmed) return;
|
||||
await deleteModel(modelKey);
|
||||
} else if (action === "favorite") {
|
||||
if (!modelKey) return;
|
||||
await setUserFavorite(modelKey, true);
|
||||
} else if (action === "unfavorite") {
|
||||
if (!modelKey) return;
|
||||
await setUserFavorite(modelKey, false);
|
||||
}
|
||||
|
||||
await fetchStatus();
|
||||
@@ -397,6 +440,14 @@ function bindDomHandlers() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.id === "mm-favorite-model-select") {
|
||||
const modelKey = safeText(target.value, "");
|
||||
if (!modelKey) return;
|
||||
runAction("select", modelKey).catch(() => {});
|
||||
target.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.id === "mm-sort-mode-select") {
|
||||
const value = safeText(target.value, "alphabetical");
|
||||
state.sortMode = value === "release_date" ? "release_date" : "alphabetical";
|
||||
@@ -411,6 +462,15 @@ function bindDomHandlers() {
|
||||
state.communityFavoriteFilter = "all";
|
||||
}
|
||||
}
|
||||
|
||||
if (target.id === "mm-user-filter-select") {
|
||||
const value = safeText(target.value, "all");
|
||||
if (value === "yes" || value === "no" || value === "all") {
|
||||
state.userFavoriteFilter = value;
|
||||
} else {
|
||||
state.userFavoriteFilter = "all";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -444,6 +504,8 @@ function renderActions(model) {
|
||||
function renderModelRow(model) {
|
||||
const label = safeText(model.label, safeText(model.value, "Unnamed"));
|
||||
const key = safeText(model.value, "");
|
||||
const favoriteAction = model.userFavorite ? "unfavorite" : "favorite";
|
||||
const favoriteTitle = model.userFavorite ? "Remove from your favorites" : "Add to your favorites";
|
||||
|
||||
return html`
|
||||
<div class="mm-row">
|
||||
@@ -457,11 +519,20 @@ function renderModelRow(model) {
|
||||
${state.sortMode === "release_date" ? "" : model.series ? html`<span class="mm-chip">${safeText(model.series)}</span>` : ""}
|
||||
${model.version ? html`<span class="mm-chip">Version ${safeText(model.version)}</span>` : ""}
|
||||
${model.released ? html`<span class="mm-chip">Released ${safeText(model.released)}</span>` : ""}
|
||||
${model.userFavorite ? html`<span class="mm-chip mm-chip-user-favorite">Your Favorite</span>` : ""}
|
||||
${model.communityFavorite ? html`<span class="mm-chip mm-chip-favorite">Community Favorite</span>` : ""}
|
||||
${model.partial ? html`<span class="mm-chip mm-chip-warning">Partial Files</span>` : ""}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mm-row-actions">
|
||||
<button
|
||||
class="mm-icon-btn ${model.userFavorite ? "is-active" : ""}"
|
||||
data-mm-action="${favoriteAction}"
|
||||
data-model="${key}"
|
||||
title="${favoriteTitle}"
|
||||
aria-label="${favoriteTitle}">
|
||||
<i class="bi ${model.userFavorite ? "bi-star-fill" : "bi-star"}"></i>
|
||||
</button>
|
||||
${renderActions(model)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -525,6 +596,7 @@ export function ModelManager() {
|
||||
<div class="mm-status">
|
||||
<span class="mm-chip">Current: ${getCurrentModelName()}</span>
|
||||
<span class="mm-chip">Progress: ${safeText(state.status.progress, "Idle")}</span>
|
||||
<span class="mm-chip">${() => getUserFavoriteModels(false).length} personal favorites</span>
|
||||
${() => state.status.isOnroad ? html`<span class="mm-chip mm-chip-warning">Onroad: actions disabled</span>` : ""}
|
||||
</div>
|
||||
|
||||
@@ -548,6 +620,23 @@ export function ModelManager() {
|
||||
})()}
|
||||
</select>
|
||||
|
||||
<label class="mm-filter-label" for="mm-favorite-model-select">Favorite Models</label>
|
||||
<select class="mm-select" id="mm-favorite-model-select" disabled="${() => getUserFavoriteModels(true).length === 0}">
|
||||
${(() => {
|
||||
const favorites = getUserFavoriteModels(true);
|
||||
return favorites.length > 0
|
||||
? html`
|
||||
<option value="">Choose a favorite</option>
|
||||
${favorites.map(model => html`
|
||||
<option value="${safeText(model.value)}">
|
||||
${safeText(model.label, model.value)}
|
||||
</option>
|
||||
`)}
|
||||
`
|
||||
: html`<option value="">No installed favorites</option>`;
|
||||
})()}
|
||||
</select>
|
||||
|
||||
<label class="mm-filter-label" for="mm-sort-mode-select">Sort</label>
|
||||
<select class="mm-select" id="mm-sort-mode-select">
|
||||
<option value="alphabetical" selected="${() => state.sortMode === "alphabetical" || false}">Alphabetical</option>
|
||||
@@ -556,6 +645,13 @@ export function ModelManager() {
|
||||
|
||||
<div class="mm-filter-break"></div>
|
||||
|
||||
<label class="mm-filter-label" for="mm-user-filter-select">Your Favorite</label>
|
||||
<select class="mm-select" id="mm-user-filter-select">
|
||||
<option value="all" selected="${() => state.userFavoriteFilter === "all" || false}">All</option>
|
||||
<option value="yes" selected="${() => state.userFavoriteFilter === "yes" || false}">Yes</option>
|
||||
<option value="no" selected="${() => state.userFavoriteFilter === "no"}">No</option>
|
||||
</select>
|
||||
|
||||
<label class="mm-filter-label" for="mm-community-filter-select">Community Favorite</label>
|
||||
<select class="mm-select" id="mm-community-filter-select">
|
||||
<option value="all" selected="${() => state.communityFavoriteFilter === "all" || false}">All</option>
|
||||
|
||||
Reference in New Issue
Block a user