feat: complete dual-seat whole-vehicle orchestration

This commit is contained in:
vee
2026-08-12 15:12:40 +08:00
parent 32379a1573
commit 9ce64b3a01
62 changed files with 9741 additions and 3950 deletions
@@ -303,6 +303,7 @@ class ModeProfile:
common: bool = True
vehicle_ids: tuple[str, ...] = ()
timeline: ModeTimeline | None = None
user_defined: bool = False
@classmethod
def from_yaml(cls, path: Path, profile_root: Path | None = None) -> "ModeProfile":
@@ -336,6 +337,7 @@ class ModeProfile:
if isinstance(raw_timeline, dict)
else None
),
user_defined=_is_user_profile_path(path, profile_root),
)
def public_dict(self) -> dict[str, Any]:
@@ -352,6 +354,8 @@ class ModeProfile:
"vehicle_ids": list(self.vehicle_ids),
},
"behavior": "cyclic" if self.timeline is not None else "static",
"origin": "user" if self.user_defined else "preset",
"deletable": self.user_defined,
}
if self.timeline is not None:
result["timeline"] = self.timeline.public_dict()
@@ -380,9 +384,11 @@ class ModeProfileRegistry:
return list(self._profiles)
def public_modes(self, vehicle_id: str | None = None) -> list[dict[str, Any]]:
profiles = self._profiles_for_vehicle(vehicle_id)
profiles.sort(key=lambda profile: 0 if profile.user_defined else 1)
return [
profile.public_dict()
for profile in self._profiles_for_vehicle(vehicle_id)
for profile in profiles
]
def match_text(
@@ -467,6 +473,45 @@ class ModeProfileRegistry:
self.reload()
return ModeProfile.from_yaml(path, self.profile_root)
def delete_user_mode(
self,
mode_id: str,
vehicle_id: str | None = None,
) -> ModeProfile:
clean_id = str(mode_id).strip()
if not clean_id:
raise ValueError("mode id must not be empty")
profile = next(
(
item
for item in self._profiles_for_vehicle(vehicle_id)
if item.id == clean_id
),
None,
)
if profile is None:
raise KeyError(clean_id)
if not profile.user_defined:
raise PermissionError("preset mode profiles cannot be deleted")
path = Path(profile.source).resolve()
profile_root = self.profile_root.resolve()
try:
relative_parts = path.relative_to(profile_root).parts
except ValueError as exc:
raise PermissionError("mode profile is outside the configured registry") from exc
if "user" not in relative_parts or path.suffix.lower() != ".yaml":
raise PermissionError("only user-defined mode profiles can be deleted")
if not path.is_file():
raise KeyError(clean_id)
path.unlink()
backup_path = _initial_backup_path(path)
if backup_path.is_file():
backup_path.unlink()
self.reload()
return profile
def update_mode(
self,
profile: ModeProfile,
@@ -623,6 +668,16 @@ def _mode_scope(
return True, ()
def _is_user_profile_path(path: Path, profile_root: Path | None) -> bool:
if profile_root is None:
return path.parent.name == "user"
try:
relative_parts = path.resolve().relative_to(profile_root.resolve()).parts
except ValueError:
return False
return "user" in relative_parts
def _is_visible_for_vehicle(
profile: ModeProfile,
vehicle_id: str | None,