优化后端和前端
This commit is contained in:
+215
-26
@@ -362,7 +362,14 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
||||
if path.startswith("/api/mode-profiles/"):
|
||||
self._handle_mode_profile_post(path)
|
||||
return
|
||||
if path != "/api/requests":
|
||||
supplement_match = re.fullmatch(
|
||||
r"/api/requests/(?P<request_id>req-[a-z0-9-]+)/supplement",
|
||||
path,
|
||||
)
|
||||
if (
|
||||
path not in {"/api/requests", "/api/requests/preview"}
|
||||
and not supplement_match
|
||||
):
|
||||
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
|
||||
return
|
||||
|
||||
@@ -397,40 +404,82 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
||||
payload = self._read_json_body()
|
||||
utterance = str(payload.get("utterance", ""))
|
||||
source = str(payload.get("client_type", "head_unit"))
|
||||
self.vehicle_display_sync.update_task(
|
||||
status="running",
|
||||
utterance=utterance,
|
||||
source=source,
|
||||
if utterance:
|
||||
self.vehicle_display_sync.update_task(
|
||||
status="running",
|
||||
utterance=utterance,
|
||||
source=source,
|
||||
)
|
||||
hardware_client_id = (
|
||||
str(payload["hardware_client_id"])
|
||||
if payload.get("hardware_client_id")
|
||||
else None
|
||||
)
|
||||
result = self.service.process(
|
||||
utterance,
|
||||
str(payload.get("provider", "auto")),
|
||||
str(payload["conversation_id"]) if payload.get("conversation_id") else None,
|
||||
str(payload.get("execution_mode", "dry_run")),
|
||||
reasoning_effort = (
|
||||
str(payload["reasoning_effort"])
|
||||
if payload.get("reasoning_effort") is not None
|
||||
else None
|
||||
)
|
||||
common_arguments = {
|
||||
"utterance": utterance,
|
||||
"provider": str(payload.get("provider", "auto")),
|
||||
"conversation_id": (
|
||||
str(payload["conversation_id"])
|
||||
if payload.get("conversation_id")
|
||||
else None
|
||||
),
|
||||
"execution_mode": str(
|
||||
payload.get("execution_mode", "dry_run")
|
||||
),
|
||||
# Hardware authorization is a server deployment policy. The
|
||||
# browser no longer renders or transmits an approval secret.
|
||||
approval_token=os.getenv("COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN"),
|
||||
vehicle_id=(
|
||||
# browser never receives or transmits this secret.
|
||||
"approval_token": os.getenv(
|
||||
"COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN"
|
||||
),
|
||||
"vehicle_id": (
|
||||
str(payload["vehicle_id"])
|
||||
if payload.get("vehicle_id")
|
||||
else None
|
||||
),
|
||||
simulation_fault=str(payload.get("simulation_fault", "normal")),
|
||||
hardware_client_id=(
|
||||
str(payload["hardware_client_id"])
|
||||
if payload.get("hardware_client_id")
|
||||
else None
|
||||
"simulation_fault": str(
|
||||
payload.get("simulation_fault", "normal")
|
||||
),
|
||||
hardware_access_bypass=self._is_unproxied_loopback_request(),
|
||||
reasoning_effort=(
|
||||
str(payload["reasoning_effort"])
|
||||
if payload.get("reasoning_effort") is not None
|
||||
else None
|
||||
),
|
||||
control_transport=str(
|
||||
"hardware_client_id": hardware_client_id,
|
||||
"hardware_access_bypass": self._is_unproxied_loopback_request(),
|
||||
"reasoning_effort": reasoning_effort,
|
||||
"control_transport": str(
|
||||
payload.get("control_transport", "wired_can")
|
||||
),
|
||||
)
|
||||
}
|
||||
if "application/x-ndjson" in self.headers.get("Accept", ""):
|
||||
self._stream_control_request(
|
||||
path,
|
||||
supplement_match,
|
||||
payload,
|
||||
common_arguments,
|
||||
source=source,
|
||||
)
|
||||
return
|
||||
if path == "/api/requests/preview":
|
||||
result = self.service.preview_native_can(**common_arguments)
|
||||
elif supplement_match:
|
||||
result = self.service.supplement_native_can(
|
||||
supplement_match.group("request_id"),
|
||||
provider=(
|
||||
str(payload["provider"])
|
||||
if payload.get("provider")
|
||||
else None
|
||||
),
|
||||
approval_token=os.getenv(
|
||||
"COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN"
|
||||
),
|
||||
hardware_client_id=hardware_client_id,
|
||||
hardware_access_bypass=self._is_unproxied_loopback_request(),
|
||||
reasoning_effort=reasoning_effort,
|
||||
)
|
||||
else:
|
||||
result = self.service.process(**common_arguments)
|
||||
utterance = str(result.get("utterance") or utterance)
|
||||
self.vehicle_display_sync.update_task(
|
||||
status=str(result.get("status", "succeeded")),
|
||||
utterance=utterance,
|
||||
@@ -489,6 +538,146 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
||||
HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
def _stream_control_request(
|
||||
self,
|
||||
path: str,
|
||||
supplement_match: re.Match[str] | None,
|
||||
payload: dict[str, object],
|
||||
common_arguments: dict[str, object],
|
||||
*,
|
||||
source: str,
|
||||
) -> None:
|
||||
"""Stream validated command plans before physical execution finishes."""
|
||||
|
||||
self.send_response(HTTPStatus.OK)
|
||||
self.send_header("Content-Type", "application/x-ndjson; charset=utf-8")
|
||||
self.send_header("Cache-Control", "no-store")
|
||||
self.send_header("X-Accel-Buffering", "no")
|
||||
self._send_security_headers()
|
||||
self.end_headers()
|
||||
stream_open = True
|
||||
|
||||
def emit(event: dict[str, object]) -> None:
|
||||
nonlocal stream_open
|
||||
if not stream_open:
|
||||
return
|
||||
content = (json.dumps(event, ensure_ascii=False) + "\n").encode(
|
||||
"utf-8"
|
||||
)
|
||||
try:
|
||||
self.wfile.write(content)
|
||||
self.wfile.flush()
|
||||
except (BrokenPipeError, ConnectionResetError):
|
||||
# The submitted control request must continue even if the
|
||||
# browser closes while the physical execution is in progress.
|
||||
stream_open = False
|
||||
|
||||
def on_commands_validated(result: dict[str, object]) -> None:
|
||||
emit({"type": "validated_commands", "payload": result})
|
||||
|
||||
utterance = str(common_arguments.get("utterance") or "")
|
||||
try:
|
||||
if path == "/api/requests/preview":
|
||||
result = self.service.preview_native_can(
|
||||
**common_arguments,
|
||||
on_commands_validated=on_commands_validated,
|
||||
)
|
||||
elif supplement_match:
|
||||
result = self.service.supplement_native_can(
|
||||
supplement_match.group("request_id"),
|
||||
provider=(
|
||||
str(payload["provider"])
|
||||
if payload.get("provider")
|
||||
else None
|
||||
),
|
||||
approval_token=os.getenv(
|
||||
"COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN"
|
||||
),
|
||||
hardware_client_id=(
|
||||
str(payload["hardware_client_id"])
|
||||
if payload.get("hardware_client_id")
|
||||
else None
|
||||
),
|
||||
hardware_access_bypass=self._is_unproxied_loopback_request(),
|
||||
reasoning_effort=(
|
||||
str(payload["reasoning_effort"])
|
||||
if payload.get("reasoning_effort") is not None
|
||||
else None
|
||||
),
|
||||
on_commands_validated=on_commands_validated,
|
||||
)
|
||||
else:
|
||||
result = self.service.process(
|
||||
**common_arguments,
|
||||
_on_commands_validated=on_commands_validated,
|
||||
)
|
||||
utterance = str(result.get("utterance") or utterance)
|
||||
self.vehicle_display_sync.update_task(
|
||||
status=str(result.get("status", "succeeded")),
|
||||
utterance=utterance,
|
||||
source=source,
|
||||
result=result,
|
||||
)
|
||||
emit({"type": "result", "payload": result})
|
||||
except HardwareAccessRequiredError as exc:
|
||||
self.vehicle_display_sync.update_task(
|
||||
status="failed",
|
||||
utterance=utterance,
|
||||
source=source,
|
||||
error={"code": "hardware_access_required", "message": str(exc)},
|
||||
)
|
||||
emit({
|
||||
"type": "error",
|
||||
"error": "hardware_access_required",
|
||||
"message": str(exc),
|
||||
"access_state": exc.state,
|
||||
})
|
||||
except HardwareBusyError as exc:
|
||||
self.vehicle_display_sync.update_task(
|
||||
status="failed",
|
||||
utterance=utterance,
|
||||
source=source,
|
||||
error={"code": "hardware_busy", "message": str(exc)},
|
||||
)
|
||||
emit({
|
||||
"type": "error",
|
||||
"error": "hardware_busy",
|
||||
"message": str(exc),
|
||||
"retry_after_seconds": exc.retry_after_seconds,
|
||||
})
|
||||
except HardwareSafetyError as exc:
|
||||
self.vehicle_display_sync.update_task(
|
||||
status="failed",
|
||||
utterance=utterance,
|
||||
source=source,
|
||||
error={"code": "hardware_safety_gate", "message": str(exc)},
|
||||
)
|
||||
emit({
|
||||
"type": "error",
|
||||
"error": "hardware_safety_gate",
|
||||
"message": str(exc),
|
||||
})
|
||||
except (ValueError, json.JSONDecodeError) as exc:
|
||||
emit({
|
||||
"type": "error",
|
||||
"error": "invalid_request",
|
||||
"message": str(exc),
|
||||
})
|
||||
except OpenAIIntentError as exc:
|
||||
self.log_error("OpenAI intent request failed: %s", exc)
|
||||
emit({
|
||||
"type": "error",
|
||||
"error": "openai_request_failed",
|
||||
"message": "GPT 意图解析失败,请检查 API Key、模型权限或网络状态。",
|
||||
})
|
||||
except Exception as exc: # noqa: BLE001 - stream a terminal event
|
||||
self.log_error("streaming request failed: %s", exc)
|
||||
emit({
|
||||
"type": "error",
|
||||
"error": "internal_error",
|
||||
"message": "请求处理失败,请查看服务端日志。",
|
||||
})
|
||||
|
||||
def do_DELETE(self) -> None: # noqa: N802 - stdlib handler API
|
||||
parsed_url = urlparse(self.path)
|
||||
path = unquote(parsed_url.path)
|
||||
|
||||
Reference in New Issue
Block a user