feat(n7web): add pet NFC workflow and glass UI

This commit is contained in:
Miayui
2026-09-14 15:16:02 +08:00
parent 812e67422d
commit aa6f59d3a6
20 changed files with 2724 additions and 102 deletions
+121 -3
View File
@@ -48,6 +48,7 @@ from cognitive_agent.application.mode_profile_intent import ( # noqa: E402
normalize_mode_profile_name,
parse_mode_profile_update,
)
from cognitive_agent.application.pet_cards import PetCardService # noqa: E402
from cognitive_agent.application.seat_service import ( # noqa: E402
LOCAL_CONTROL_BUDGET_MS,
SeatDemoService,
@@ -217,6 +218,10 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
gesture = GestureCapabilityBridge(REPOSITORY_ROOT)
extension = SelfExtensionService()
nfc = NfcReaderService()
pets = PetCardService(
nfc_provider=nfc.snapshot,
publisher=service.publish_pet_message,
)
calibration = CalibrationService(
REPOSITORY_ROOT / "artifacts" / "tmp" / "self-calibration",
repository_root=REPOSITORY_ROOT,
@@ -393,7 +398,16 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
self._json(self._extension_health())
return
if path == "/api/extension/nfc/status":
self._json({"success": True, **self.nfc.snapshot()})
self._json(
{
"success": True,
**self.nfc.snapshot(),
"pet_cards": self.pets.snapshot(),
}
)
return
if path == "/api/extension/pets":
self._json({"success": True, **self.pets.snapshot()})
return
if path == "/api/extension/gscu/slots":
try:
@@ -2488,6 +2502,41 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
return
if not self._require_same_origin() or not self._require_json_content_type():
return
pet_match = re.fullmatch(
r"/api/extension/pets/(?P<pet_id>pet-[0-9a-f]{32})",
path,
)
if pet_match is not None:
try:
payload = self._read_json_body(max_bytes=4096)
unexpected = set(payload) - {"pet_name", "action"}
if unexpected:
if "nfc_id" in unexpected:
raise ValueError(
"nfc_id 只能通过当前已识别的 NFC 卡片完成绑定。"
)
raise ValueError("宠物卡片包含不支持的字段。")
card = self.pets.update_card(
pet_match.group("pet_id"),
pet_name=payload.get("pet_name"),
action=payload.get("action"),
)
self._json(
{"success": True, "card": card, **self.pets.snapshot()},
HTTPStatus.OK,
)
except KeyError:
self._json(
{"success": False, "error": "not_found", "message": "未找到宠物卡片。"},
HTTPStatus.NOT_FOUND,
)
except (ValueError, json.JSONDecodeError) as exc:
self._json(
{"success": False, "error": "invalid_request", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
return
match = re.fullmatch(r"/api/extension/gscu/slots/(?P<slot>\d+)", path)
if match is None:
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
@@ -2524,6 +2573,28 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
HTTPStatus.BAD_REQUEST,
)
return
pet_match = re.fullmatch(
r"/api/extension/pets/(?P<pet_id>pet-[0-9a-f]{32})",
path,
)
if pet_match is not None:
try:
deleted = self.pets.delete_card(pet_match.group("pet_id"))
self._json(
{"success": True, "deleted": deleted, **self.pets.snapshot()},
HTTPStatus.OK,
)
except KeyError:
self._json(
{"success": False, "error": "not_found", "message": "未找到宠物卡片。"},
HTTPStatus.NOT_FOUND,
)
except ValueError as exc:
self._json(
{"success": False, "error": "invalid_request", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
return
prefix = "/api/mode-profiles/"
if not path.startswith(prefix):
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
@@ -2833,6 +2904,47 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
}
try:
payload = self._read_json_body(max_bytes=256 * 1024)
if path == "/api/extension/pets":
if "nfc_id" in payload:
raise ValueError(
"nfc_id 只能通过当前已识别的 NFC 卡片完成绑定。"
)
card = self.pets.create_card(
payload.get("pet_name"),
payload.get("action"),
)
self._json(
{"success": True, "card": card, **self.pets.snapshot()},
HTTPStatus.CREATED,
)
return
pet_binding_match = re.fullmatch(
r"/api/extension/pets/(?P<pet_id>pet-[0-9a-f]{32})/"
r"(?P<action>bind-current-nfc|unbind)",
path,
)
if pet_binding_match is not None:
pet_id = pet_binding_match.group("pet_id")
try:
if pet_binding_match.group("action") == "bind-current-nfc":
card = self.pets.bind_current_nfc(pet_id)
else:
card = self.pets.unbind_card(pet_id)
except KeyError:
self._json(
{
"success": False,
"error": "not_found",
"message": "未找到宠物卡片。",
},
HTTPStatus.NOT_FOUND,
)
return
self._json(
{"success": True, "card": card, **self.pets.snapshot()},
HTTPStatus.OK,
)
return
if path in {
"/api/extension/gscu/erase",
"/api/extension/gscu/erase-all",
@@ -3205,7 +3317,11 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
)
def _extension_health(self) -> dict[str, object]:
return {**self.extension.health(), "nfc": self.nfc.snapshot()}
return {
**self.extension.health(),
"nfc": self.nfc.snapshot(),
"pet_cards": self.pets.snapshot(),
}
def _handle_hardware_access_post(self, path: str) -> None:
local_path = path.startswith("/api/local/")
@@ -3848,6 +3964,7 @@ def main() -> None:
SeatDemoHandler.service.start()
SeatDemoHandler.extension.start()
SeatDemoHandler.nfc.start()
SeatDemoHandler.pets.start()
SeatDemoHandler.calibration.start()
SeatDemoHandler.repair.start()
if args.host in {"0.0.0.0", "::"}:
@@ -3900,9 +4017,10 @@ def main() -> None:
server.server_close()
SeatDemoHandler.speech.close()
SeatDemoHandler.assistant.close()
SeatDemoHandler.pets.close()
SeatDemoHandler.nfc.close()
SeatDemoHandler.service.close()
SeatDemoHandler.extension.close()
SeatDemoHandler.nfc.close()
SeatDemoHandler.calibration.close()
SeatDemoHandler.repair.close()