优化后端

This commit is contained in:
Miayui
2026-09-11 17:56:56 +08:00
parent 7eed11b774
commit 812e67422d
7299 changed files with 1744959 additions and 112 deletions
+178 -9
View File
@@ -92,6 +92,24 @@ const REQUEST_TIMEOUT_MS = 45 * 1000;
const DEFAULT_PHYSICAL_STATE_POLL_INTERVAL_MS = 5000;
const AUTHORITATIVE_CH1_SOURCES = new Set(["physical_can_ch1", "wireless_mqtt_ch1"]);
async function apiResponseError(response) {
let payload = null;
try {
payload = await response.json();
} catch (_error) {
// Some proxies return an empty or non-JSON error page. Preserve the HTTP
// status even when no structured reason is available.
}
const status = Number(response?.status) || 0;
const serverMessage = String(payload?.message || payload?.error || "").trim();
const fallback = status ? `HTTP ${status}` : "请求失败";
const error = new Error(serverMessage ? `${fallback}:${serverMessage}` : fallback);
error.status = status;
error.code = String(payload?.error || "").trim();
error.serverMessage = serverMessage;
return error;
}
function requestTimeoutForExecutionMode(mode) {
return mode === "hardware" ? HARDWARE_REQUEST_TIMEOUT_MS : REQUEST_TIMEOUT_MS;
}
@@ -111,6 +129,8 @@ function renderCabinLiveStatus(networkStatus = state.connectionStatus) {
if (!indicator) return;
const label = $("b", indicator);
if (!label) return;
indicator.removeAttribute("aria-label");
indicator.removeAttribute("title");
const summarySystem = $("#cabinSummarySystem");
const setSummarySystem = (text, status) => {
if (!summarySystem) return;
@@ -144,6 +164,22 @@ function renderCabinLiveStatus(networkStatus = state.connectionStatus) {
}
const snapshot = state.vehicleStateSnapshot;
if (snapshot?.errorStatus === 403) {
const reason = snapshot.errorReason || "当前终端没有车辆状态读取权限";
const visibleReason = `${transportLabel} · 403:${reason}`;
indicator.dataset.state = "offline";
label.textContent = visibleReason;
indicator.title = visibleReason;
indicator.setAttribute("aria-label", visibleReason);
const telemetryState = $("#vehicleTelemetryState");
if (telemetryState) telemetryState.textContent = "403 拒绝";
setSummarySystem("403 · 状态读取被拒绝", "warning");
return;
}
const telemetryState = $("#vehicleTelemetryState");
if (telemetryState?.textContent === "403 拒绝") {
telemetryState.textContent = "只读";
}
const observedIds = Array.isArray(snapshot?.observedFrameIds)
? snapshot.observedFrameIds
: [];
@@ -5884,19 +5920,24 @@ function installModeChipScroller() {
let pointerStartScroll = 0;
let dragged = false;
let suppressClick = false;
let pointerCaptured = false;
group.addEventListener("pointerdown", (event) => {
if (event.pointerType !== "mouse" || event.button !== 0 || maximumScroll() <= 1) return;
pointerId = event.pointerId;
pointerStartX = event.clientX;
pointerStartScroll = group.scrollLeft;
dragged = false;
group.setPointerCapture?.(pointerId);
pointerCaptured = false;
});
group.addEventListener("pointermove", (event) => {
if (pointerId !== event.pointerId) return;
const distance = event.clientX - pointerStartX;
if (!dragged && Math.abs(distance) < 5) return;
dragged = true;
if (!pointerCaptured && typeof group.setPointerCapture === "function") {
group.setPointerCapture(pointerId);
pointerCaptured = true;
}
group.classList.add("is-dragging");
group.scrollLeft = Math.max(0, Math.min(maximumScroll(), pointerStartScroll - distance));
updateScrollState();
@@ -5904,8 +5945,9 @@ function installModeChipScroller() {
});
const finishPointerDrag = (event) => {
if (pointerId !== event.pointerId) return;
group.releasePointerCapture?.(pointerId);
if (pointerCaptured) group.releasePointerCapture?.(pointerId);
pointerId = null;
pointerCaptured = false;
group.classList.remove("is-dragging");
if (dragged) {
suppressClick = true;
@@ -6094,11 +6136,113 @@ function installModeActionPreview() {
window.addEventListener("resize", () => hideModeActionPopover({ immediate: true }), { passive: true });
}
function vueModeChipEntry(mode) {
const timeline = mode?.timeline;
const actionCount = modeExecutionActionViews(mode).length
|| Number(mode?.operation_count || 0);
const phaseSummary = Array.isArray(timeline?.phases)
? timeline.phases.map((phase) => {
if (Number(phase.duration_ms || 0) > 0) {
return `${phase.name} ${modeSecondsLabel(phase.duration_ms)}s`;
}
const timeout = Number(phase.timeout_ms || 0);
return timeout > 0
? `${phase.name} 目标到位(保护上限${modeSecondsLabel(timeout)}s)`
: `${phase.name} 目标到位`;
}).join(" → ")
: "";
let cycleLabel = "";
if (["infinite", "until_stopped_or_timeout"].includes(timeline?.repeat)) {
const runtimeMinutes = Number(timeline.max_runtime_ms || 0) / 60000;
const runtimeLabel = Number.isInteger(runtimeMinutes)
? String(runtimeMinutes)
: runtimeMinutes.toFixed(1);
const cycleDurationMs = Number(timeline.cycle_duration_ms || 0);
const cycle = cycleDurationMs > 0
? `${modeSecondsLabel(cycleDurationMs)}s循环`
: "目标到位循环";
cycleLabel = runtimeMinutes > 0 ? `${cycle} · 最长${runtimeLabel}min` : cycle;
}
return {
type: "mode",
key: `mode-${mode.id}`,
id: mode.id,
label: modeChipLabel(mode),
summary: modeChipSummary(mode),
icon: mode.icon || "bookmark",
command: `进入${mode.name}`,
origin: mode?.origin || "preset",
behavior: cycleLabel ? "cyclic" : "",
cycleLabel,
ariaLabel: `${mode.name || mode.id},${actionCount} 项执行动作,点击执行`,
title: [mode.description || mode.name, phaseSummary, "悬浮或聚焦查看全部执行动作"]
.filter(Boolean)
.join("\n"),
};
}
function vueModeChipEntries(modes) {
const preferredStaticOrder = [
"zero_gravity",
"office",
"nap",
"queen_passenger_driving",
"leisure_lounger",
];
const userModes = modes.filter(isUserModeProfile);
const dynamicOrder = new Map([["rocking_horse", 0], ["dynamic_cradle", 1]]);
const dynamicModes = modes
.filter((mode) => !isUserModeProfile(mode) && isDynamicModeProfile(mode))
.sort((left, right) => (
(dynamicOrder.get(left.id) ?? Number.MAX_SAFE_INTEGER)
- (dynamicOrder.get(right.id) ?? Number.MAX_SAFE_INTEGER)
));
const staticModes = modes.filter(
(mode) => !isUserModeProfile(mode) && !isDynamicModeProfile(mode),
);
const preferredStaticModes = preferredStaticOrder
.map((modeId) => staticModes.find((mode) => mode.id === modeId))
.filter(Boolean);
const otherStaticModes = staticModes.filter((mode) => !preferredStaticModes.includes(mode));
const entries = [];
const appendLabel = (label, id) => entries.push({
type: "label",
key: `label-${id}`,
id,
label,
});
const appendDivider = (id) => entries.push({
type: "divider",
key: `divider-${id}`,
id,
});
if (userModes.length) {
appendLabel("自定义模式", "custom");
entries.push(...userModes.map(vueModeChipEntry));
}
if (userModes.length && (staticModes.length || dynamicModes.length)) appendDivider("custom");
if (staticModes.length) {
appendLabel("静态模式", "static");
entries.push(...preferredStaticModes.map(vueModeChipEntry));
entries.push(...otherStaticModes.map(vueModeChipEntry));
}
if (dynamicModes.length) {
if (userModes.length || staticModes.length) appendDivider("dynamic");
appendLabel("动态模式", "dynamic");
entries.push(...dynamicModes.map(vueModeChipEntry));
}
return entries;
}
function populateModeChips() {
const group = $(".mode-chips");
if (!group) return;
const buttons = () => $$("button", group);
const modes = modeProfiles();
if (window.CognitiveAgentVueUI?.renderModeChips?.(group, vueModeChipEntries(modes))) {
populateModeGallery();
return;
}
const runtimeModeIds = new Set(modes.map((mode) => mode.id));
$$('button[data-mode-origin="user"]', group).forEach((button) => {
if (!runtimeModeIds.has(button.dataset.modeProfileId)) button.remove();
@@ -6155,6 +6299,8 @@ function populateModeChips() {
runIcon.dataset.lucide = "play";
button.append(runIcon);
}
runIcon.dataset.modeRun = "true";
runIcon.title = `执行${mode.name || mode.id}`;
const timeline = mode.timeline;
let cycleBadge = $(".mode-cycle-badge", button);
if (["infinite", "until_stopped_or_timeout"].includes(timeline?.repeat)) {
@@ -6330,8 +6476,10 @@ function setModeChipsDisabled(disabled) {
});
}
async function executeSavedModeProfile(mode) {
async function executeSavedModeProfile(mode, { command = "" } = {}) {
if (!mode?.id || !mode?.command_flow?.source_request_id) return false;
const modeCommand = String(command || `进入${mode.name || mode.id}`).trim();
const assistant = window.CognitiveAgentAssistant;
const driverOnlyCh1 = isDriverOnlyCh1Runtime();
if (state.executionMode === "hardware"
&& state.controlTransport !== "wireless_mqtt"
@@ -6346,10 +6494,13 @@ async function executeSavedModeProfile(mode) {
closeModeGallery({ restoreFocus: false });
const runButton = $("#runButton");
const input = $("#utterance");
runButton.disabled = true;
setModeChipsDisabled(true);
setModeExecutionState(mode.id, "running");
$("#utterance").value = mode.name || mode.id;
assistant?.open?.();
input.value = modeCommand;
input.dispatchEvent(new Event("input", { bubbles: true }));
setRunStatus("processing", "MODE COMMAND FLOW");
$("#resultTitle").textContent = `正在直接执行${mode.name}`;
try {
@@ -6375,11 +6526,11 @@ async function executeSavedModeProfile(mode) {
if (!payload) throw new Error("模式卡片未返回执行结果");
renderResult(payload);
const completed = ["succeeded", "partially_succeeded"].includes(payload.status);
$("#utterance").value = mode.name || mode.id;
$("#resultTitle").textContent = completed
? `${mode.name}执行完成`
: `${mode.name}执行未完成`;
setModeExecutionState(mode.id, completed ? "succeeded" : "failed");
assistant?.announceModeExecution?.(mode, payload, { command: modeCommand });
if (state.executionMode === "hardware") await synchronizePhysicalSeatState();
showToast(completed
? `${mode.name}已直接执行保存的控制指令流`
@@ -6388,8 +6539,13 @@ async function executeSavedModeProfile(mode) {
setModeExecutionState(mode.id, "failed");
setRunStatus("failed", "MODE FAILED");
$("#resultTitle").textContent = `${mode.name}执行失败`;
assistant?.announceModeExecution?.(mode, null, { command: modeCommand, error });
showToast(error.message || "模式执行失败");
} finally {
if (input.value === modeCommand) {
input.value = "";
input.dispatchEvent(new Event("input", { bubbles: true }));
}
runButton.disabled = false;
setModeChipsDisabled(false);
}
@@ -6412,6 +6568,10 @@ function applyModeCommand(command, modeProfileId = null) {
setModeRailSelection(mode);
closeModeGallery({ restoreFocus: false });
assistant?.open?.();
if (mode?.command_flow?.source_request_id) {
void executeSavedModeProfile(mode, { command: modeCommand });
return true;
}
const input = $("#utterance");
const form = $("#requestForm");
if (!input || !form) {
@@ -6468,7 +6628,7 @@ function applyRuntimeTargetPolicy(runtime) {
async function loadRuntime() {
try {
const response = await apiRequest("/api/runtime", { cache: "no-store" });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
if (!response.ok) throw await apiResponseError(response);
state.runtime = await response.json();
renderControlTransport();
applyRuntimeTargetPolicy(state.runtime);
@@ -6487,6 +6647,7 @@ async function loadRuntime() {
mqttDualChannel ? vehicleCan.configured : hardware.configured,
);
const vehicleHardwareReady = Boolean(vehicleCan.configured);
const linkedHardwareReady = seatHardwareReady && vehicleHardwareReady;
const ch1DriverOnly = vehicleHardwareReady && !seatHardwareReady;
const hardwareExecutionReady = Boolean(hardware.configured || vehicleCan.configured);
const gptButton = $('[data-provider="openai"]');
@@ -6563,7 +6724,7 @@ async function synchronizePassengerSeatState() {
window.location.replace("/login");
return false;
}
if (!response.ok) throw new Error(`HTTP ${response.status}`);
if (!response.ok) throw await apiResponseError(response);
const payload = await response.json();
if (requestedTransport !== state.controlTransport) return false;
updateWiredCanProbe("passenger", true, requestedTransport);
@@ -6605,7 +6766,7 @@ async function synchronizeDriverSeatState() {
window.location.replace("/login");
return false;
}
if (!response.ok) throw new Error(`HTTP ${response.status}`);
if (!response.ok) throw await apiResponseError(response);
const payload = await response.json();
if (requestedTransport !== state.controlTransport) return false;
updateWiredCanProbe("vehicle", true, requestedTransport);
@@ -6627,6 +6788,9 @@ async function synchronizeDriverSeatState() {
state.vehicleStateSnapshot = {
...(state.vehicleStateSnapshot || {}),
error: error.message || String(error),
errorStatus: Number(error.status) || null,
errorCode: error.code || "",
errorReason: error.serverMessage || error.message || String(error),
};
renderCabinLiveStatus();
if (!["physical_can_ch1", "wireless_mqtt_ch1"].includes(state.seatFeedbackSources.driver)) {
@@ -7950,6 +8114,10 @@ function recordVehicleStateTelemetry(payload) {
: "physical_can_ch1";
state.vehicleStateSnapshot = {
...previous,
error: null,
errorStatus: null,
errorCode: "",
errorReason: "",
source,
capturedAt: payload.captured_at || null,
receivedAt: Date.now(),
@@ -8716,7 +8884,8 @@ document.addEventListener("DOMContentLoaded", async () => {
installModeActionPreview();
$(".mode-chips").addEventListener("click", (event) => {
const target = event.target instanceof Element ? event.target : null;
const button = target?.closest("button[data-mode-command]");
const runControl = target?.closest("[data-mode-run]");
const button = (runControl || target)?.closest("button[data-mode-command]");
if (button) applyModeCommand(
button.dataset.modeCommand,
button.dataset.modeProfileId || null,