优化前端,优化前端指令,优化自定义控制

This commit is contained in:
minghanchen
2026-08-24 09:40:16 +08:00
parent fff7657fea
commit 8d05815773
130 changed files with 5543 additions and 2206 deletions
@@ -3,6 +3,7 @@
from __future__ import annotations
import hashlib
import json
import re
from dataclasses import dataclass
from pathlib import Path
@@ -13,6 +14,7 @@ import yaml
_WHITESPACE = re.compile(r"\s+")
_NON_ASCII_SLUG = re.compile(r"[^a-z0-9]+")
_MODE_ID = re.compile(r"^[a-z0-9][a-z0-9_-]{0,63}$")
def _normalize_match_text(value: str) -> str:
@@ -28,6 +30,37 @@ def _clean_name(value: str) -> str:
return cleaned
def _clean_mode_id(value: str) -> str:
cleaned = str(value).strip()
if not _MODE_ID.fullmatch(cleaned):
raise ValueError(
"mode id must start with a lowercase letter or number and contain "
"only lowercase letters, numbers, underscores, or hyphens"
)
return cleaned
def _clean_instruction(value: str) -> str:
cleaned = str(value).strip()
if len(cleaned) > 1000:
raise ValueError("mode instruction must be at most 1000 characters")
return cleaned
def _clean_command_flow(value: Any) -> dict[str, Any] | None:
if value is None:
return None
if not isinstance(value, dict):
raise ValueError("mode command_flow must be an object")
try:
encoded = json.dumps(value, ensure_ascii=False, sort_keys=True)
except (TypeError, ValueError) as exc:
raise ValueError("mode command_flow must be JSON serializable") from exc
if len(encoded.encode("utf-8")) > 1024 * 1024:
raise ValueError("mode command_flow must be at most 1 MiB")
return json.loads(encoded)
def _mode_aliases(name: str, aliases: list[str] | None = None) -> list[str]:
values = [name, *(aliases or [])]
if name.endswith("模式") and len(name) > 2:
@@ -304,6 +337,8 @@ class ModeProfile:
vehicle_ids: tuple[str, ...] = ()
timeline: ModeTimeline | None = None
user_defined: bool = False
instruction: str = ""
command_flow: dict[str, Any] | None = None
@classmethod
def from_yaml(cls, path: Path, profile_root: Path | None = None) -> "ModeProfile":
@@ -338,6 +373,8 @@ class ModeProfile:
else None
),
user_defined=_is_user_profile_path(path, profile_root),
instruction=_clean_instruction(document.get("instruction", "")),
command_flow=_clean_command_flow(document.get("command_flow")),
)
def public_dict(self) -> dict[str, Any]:
@@ -356,6 +393,8 @@ class ModeProfile:
"behavior": "cyclic" if self.timeline is not None else "static",
"origin": "user" if self.user_defined else "preset",
"deletable": self.user_defined,
"instruction": self.instruction,
"command_flow": self.command_flow,
}
if self.timeline is not None:
result["timeline"] = self.timeline.public_dict()
@@ -397,10 +436,19 @@ class ModeProfileRegistry:
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 profiles
]
preset_ids = {
profile.id
for profile in self._visible_candidates_for_vehicle(vehicle_id)
if not profile.user_defined
}
public = []
for profile in profiles:
item = profile.public_dict()
if profile.user_defined and profile.id in preset_ids:
item["origin"] = "customized"
item["preset_override"] = True
public.append(item)
return public
def match_text(
self,
@@ -442,6 +490,12 @@ class ModeProfileRegistry:
mode_id: str | None = None,
aliases: list[str] | None = None,
vehicle_id: str | None = None,
*,
description: str | None = None,
icon: str = "bookmark",
instruction: str = "",
command_flow: dict[str, Any] | None = None,
timeline: ModeTimeline | None = None,
) -> ModeProfile:
clean_name = _clean_name(name)
canonical_operations = [
@@ -452,7 +506,9 @@ class ModeProfileRegistry:
]
if not canonical_operations:
raise ValueError("mode profile must contain at least one operation")
profile_id = mode_id or _slugify_mode_id(clean_name)
profile_id = _clean_mode_id(mode_id or _slugify_mode_id(clean_name))
clean_instruction = _clean_instruction(instruction)
clean_command_flow = _clean_command_flow(command_flow)
scope = (
{"vehicle_ids": [vehicle_id]}
if vehicle_id
@@ -462,13 +518,19 @@ class ModeProfileRegistry:
"id": profile_id,
"name": clean_name,
"aliases": _mode_aliases(clean_name, aliases),
"description": "用户固化的长期模式配置。",
"icon": "bookmark",
"description": str(description or "用户固化的长期模式配置。")[:500],
"icon": str(icon or "bookmark")[:64],
"scope": scope,
"operations": [
operation.to_yaml() for operation in canonical_operations
],
}
if clean_instruction:
document["instruction"] = clean_instruction
if clean_command_flow is not None:
document["command_flow"] = clean_command_flow
if timeline is not None:
document["timeline"] = timeline.to_yaml()
path = (
self.user_root / "vehicles" / vehicle_id / "user" / f"{profile_id}.yaml"
if vehicle_id
@@ -653,6 +715,10 @@ def _mode_profile_document(
}
if profile.timeline is not None:
document["timeline"] = profile.timeline.to_yaml()
if profile.instruction:
document["instruction"] = profile.instruction
if profile.command_flow is not None:
document["command_flow"] = profile.command_flow
return document