feat(n7web): refine spatial UI and NFC-gated extension routing
This commit is contained in:
@@ -568,7 +568,11 @@
|
||||
const verificationStatus = String(telemetry.verification_status || "").toLowerCase();
|
||||
const failureMessage = String(execution?.message || "").trim();
|
||||
const aborted = abortedStates.has(executionStatus);
|
||||
const explicitlyBlocked = executionStatus === "blocked"
|
||||
|| verificationStatus === "blocked"
|
||||
|| Boolean(telemetry.authorization_reason);
|
||||
const executionFailed = executionStatus === "failed"
|
||||
|| explicitlyBlocked
|
||||
|| verificationStatus === "failed_or_blocked";
|
||||
const missingWindowFeedback = /0x388/i.test(failureMessage) && (
|
||||
/no fresh valid/i.test(failureMessage)
|
||||
@@ -579,7 +583,8 @@
|
||||
|| /未收到.*(?:有效|新鲜).*遮阳帘.*位置/.test(failureMessage)
|
||||
);
|
||||
const safetyBlocked = executionFailed && !submitted && (
|
||||
missingWindowFeedback
|
||||
explicitlyBlocked
|
||||
|| missingWindowFeedback
|
||||
|| missingShadeFeedback
|
||||
|| /refusing (?:blind )?motion/i.test(failureMessage)
|
||||
|| /拒绝(?:启动机构|盲动|执行)/.test(failureMessage)
|
||||
@@ -1100,9 +1105,9 @@
|
||||
sidebarToggle.setAttribute("aria-expanded", "false");
|
||||
sidebarToggle.title = "展开左侧控制台";
|
||||
|
||||
const footer = make("footer", "v2-sidebar-footer", `
|
||||
const footer = make("footer", "v2-sidebar-footer v2-control-link-card", `
|
||||
<span class="v2-live-dot"></span>
|
||||
<div><strong>智舱控制链路</strong><small id="v2ConnectionText">正在连接服务</small></div>
|
||||
<div><strong>智舱控制链路</strong><small id="v2ConnectionText" aria-live="polite">正在连接服务</small></div>
|
||||
<button type="button" id="v2LogoutButton" title="退出登录" aria-label="退出登录"><i data-lucide="log-out"></i></button>`);
|
||||
|
||||
commandPanel.append(sidebarToggle, footer);
|
||||
@@ -1675,6 +1680,11 @@
|
||||
if (!list) return;
|
||||
const filter = $("#v2LogFilter")?.value || "all";
|
||||
const items = displaySync.logs.filter((item) => filter === "all" || item.category === filter);
|
||||
const count = $("#v2SyncLogCount");
|
||||
if (count) {
|
||||
count.textContent = String(items.length);
|
||||
count.title = `${items.length} 条记录`;
|
||||
}
|
||||
const icons = { task: "route", sync: "refresh-cw", extension: "usb", device: "activity" };
|
||||
list.innerHTML = items.length ? items.map((item) => `
|
||||
<li data-log-status="${escapeHtml(item.status)}">
|
||||
@@ -1685,6 +1695,21 @@
|
||||
window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } });
|
||||
}
|
||||
|
||||
function setSyncLogCollapsed(collapsed) {
|
||||
const panel = $(".v2-sync-log");
|
||||
const list = $("#v2SyncLogList");
|
||||
const toggle = $("#v2SyncLogToggle");
|
||||
if (!panel || !list || !toggle) return;
|
||||
const nextCollapsed = Boolean(collapsed);
|
||||
const label = nextCollapsed ? "展开运行日志与同步记录" : "折叠运行日志与同步记录";
|
||||
panel.dataset.collapsed = String(nextCollapsed);
|
||||
list.hidden = nextCollapsed;
|
||||
toggle.setAttribute("aria-expanded", String(!nextCollapsed));
|
||||
toggle.setAttribute("aria-label", label);
|
||||
toggle.title = label;
|
||||
requestAnimationFrame(syncSidebarLogBaseline);
|
||||
}
|
||||
|
||||
function panelVisible(key) {
|
||||
const panel = key === "devices" ? $("#v2DevicePanel") : $("#v2ExtensionPanel");
|
||||
if (panel) return !panel.classList.contains("is-collapsed");
|
||||
@@ -2439,9 +2464,15 @@
|
||||
const sidebarFooter = $(".v2-sidebar-footer", sidebar);
|
||||
const syncSidebar = make("section", "v2-sync-sidebar", `
|
||||
<div class="v2-sync-sidebar-overview"></div>
|
||||
<section class="v2-sync-log" aria-labelledby="v2SyncLogTitle">
|
||||
<header><div><h3 id="v2SyncLogTitle">运行日志 / 同步记录</h3></div><select id="v2LogFilter" aria-label="筛选同步日志"><option value="all">全部</option><option value="task">任务</option><option value="sync">同步</option><option value="extension">外设</option><option value="device">设备</option></select></header>
|
||||
<ol id="v2SyncLogList" aria-live="polite"></ol>
|
||||
<section class="v2-sync-log" aria-labelledby="v2SyncLogTitle" data-collapsed="true">
|
||||
<header>
|
||||
<div><h3 id="v2SyncLogTitle">运行日志 / 同步记录</h3></div>
|
||||
<div class="v2-sync-log-actions">
|
||||
<select id="v2LogFilter" aria-label="筛选同步日志"><option value="all">全部</option><option value="task">任务</option><option value="sync">同步</option><option value="extension">外设</option><option value="device">设备</option></select>
|
||||
<button type="button" class="v2-sync-log-toggle" id="v2SyncLogToggle" aria-controls="v2SyncLogList" aria-expanded="false" aria-label="展开运行日志与同步记录" title="展开运行日志与同步记录"><span id="v2SyncLogCount">0</span><i data-lucide="chevron-down"></i></button>
|
||||
</div>
|
||||
</header>
|
||||
<ol id="v2SyncLogList" aria-live="polite" hidden></ol>
|
||||
</section>`);
|
||||
if (sidebar && sidebarFooter) {
|
||||
sidebar.insertBefore(syncSidebar, sidebarFooter);
|
||||
@@ -2574,6 +2605,10 @@
|
||||
if (event.target.matches("select[data-device-control]")) storeDeviceControlDraft(event.target);
|
||||
});
|
||||
$("#v2LogFilter")?.addEventListener("change", renderSyncLogs);
|
||||
$("#v2SyncLogToggle")?.addEventListener("click", () => {
|
||||
setSyncLogCollapsed($(".v2-sync-log")?.dataset.collapsed !== "true");
|
||||
});
|
||||
setSyncLogCollapsed(true);
|
||||
const beginPendingControlRequest = (request = {}) => {
|
||||
beginCommandTiming(request);
|
||||
resetValidatedCommandContext();
|
||||
@@ -2879,18 +2914,38 @@
|
||||
const connectionText = $("#v2ConnectionText");
|
||||
if (connectionText) {
|
||||
const status = connectionState?.status || "connecting";
|
||||
const labels = {
|
||||
connecting: "正在连接服务",
|
||||
online: "服务已连接",
|
||||
degraded: "连接不稳定",
|
||||
offline: "服务已断开",
|
||||
};
|
||||
connectionText.textContent = labels[status] || labels.connecting;
|
||||
connectionText.title = connectionState?.lastError || connectionText.textContent;
|
||||
$(".v2-sidebar-footer")?.classList.toggle(
|
||||
"offline",
|
||||
status === "offline" || status === "degraded",
|
||||
);
|
||||
const selectedTransport = $('.vehicle-transport-options [data-control-transport][aria-pressed="true"]')
|
||||
|| $('.vehicle-transport-options [data-control-transport].active');
|
||||
const transport = selectedTransport?.dataset.controlTransport || "";
|
||||
const transportOnline = selectedTransport?.dataset.connectionState === "online";
|
||||
const transportLabel = transport === "wireless_mqtt" ? "无线 CAN" : "有线 CAN";
|
||||
const footer = connectionText.closest(".v2-control-link-card");
|
||||
let presentationState = status;
|
||||
let label = "正在连接服务";
|
||||
|
||||
if (status === "online" && transport) {
|
||||
presentationState = transportOnline ? "online" : "offline";
|
||||
label = transport === "wireless_mqtt"
|
||||
? `${transportLabel} · MQTT ${transportOnline ? "已连接" : "未连接"}`
|
||||
: `${transportLabel} · 信号链路${transportOnline ? "正常" : "断开"}`;
|
||||
} else if (status === "online") {
|
||||
presentationState = "online";
|
||||
label = "服务已连接";
|
||||
} else if (status === "degraded") {
|
||||
label = transport ? `${transportLabel} · 主服务连接不稳定` : "主服务连接不稳定";
|
||||
} else if (status === "offline") {
|
||||
label = transport ? `${transportLabel} · 主服务已断开` : "主服务已断开";
|
||||
}
|
||||
|
||||
connectionText.textContent = label;
|
||||
connectionText.title = connectionState?.lastError
|
||||
|| (!transportOnline ? selectedTransport?.title : "")
|
||||
|| label;
|
||||
if (footer) {
|
||||
footer.dataset.transport = transport || "service";
|
||||
footer.dataset.state = presentationState;
|
||||
footer.classList.toggle("offline", presentationState !== "online");
|
||||
}
|
||||
}
|
||||
keepRunButtonAvailable();
|
||||
}
|
||||
@@ -2927,6 +2982,7 @@
|
||||
syncRuntimePresentation(event.detail);
|
||||
});
|
||||
window.addEventListener("cognitive-agent:vehicle-feedback-state", () => {
|
||||
syncRuntimePresentation();
|
||||
renderSyncedDevices();
|
||||
if (displaySync.activeDevice) {
|
||||
presentTwinDevice(displaySync.activeDevice, {
|
||||
@@ -2935,6 +2991,14 @@
|
||||
});
|
||||
}
|
||||
});
|
||||
const transportOptions = $(".vehicle-transport-options");
|
||||
if (transportOptions) {
|
||||
new MutationObserver(() => syncRuntimePresentation()).observe(transportOptions, {
|
||||
attributes: true,
|
||||
attributeFilter: ["aria-pressed", "class", "data-connection-state", "title"],
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
const observer = new MutationObserver(() => keepRunButtonAvailable());
|
||||
[$("#hardwareMode"), $("#simulationMode")]
|
||||
.filter(Boolean)
|
||||
|
||||
Reference in New Issue
Block a user