feat(web-console): rebuild intelligent cockpit sync interface
This commit is contained in:
+557
-197
@@ -11,25 +11,11 @@
|
||||
|
||||
const $ = (selector, root = document) => root.querySelector(selector);
|
||||
const $$ = (selector, root = document) => [...root.querySelectorAll(selector)];
|
||||
const initialRole = sessionStorage.getItem("cognitive-agent.demo-role") === "admin"
|
||||
? "admin"
|
||||
: "user";
|
||||
const storedModule = sessionStorage.getItem("cognitive-agent.active-module");
|
||||
|
||||
const ui = {
|
||||
role: initialRole,
|
||||
controlMode: sessionStorage.getItem("cognitive-agent.control-mode") === "gesture"
|
||||
? "gesture"
|
||||
: "natural",
|
||||
activeModule: storedModule === "self-evolution"
|
||||
? "self-evolution"
|
||||
: storedModule === "self-extension"
|
||||
? "self-extension"
|
||||
: storedModule === "self-calibration"
|
||||
? "self-calibration"
|
||||
: storedModule === "self-repair"
|
||||
? "self-repair"
|
||||
: "self-orchestration",
|
||||
activeModule: "self-orchestration",
|
||||
activePage: 0,
|
||||
sidebarCollapsed: sessionStorage.getItem("cognitive-agent.sidebar-collapsed") === "true",
|
||||
};
|
||||
@@ -44,6 +30,15 @@
|
||||
return node;
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value ?? "")
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """)
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
|
||||
function closeCustomDropdowns(except = null) {
|
||||
dropdownControls.forEach((control) => {
|
||||
if (control !== except) control.close();
|
||||
@@ -246,7 +241,7 @@
|
||||
}
|
||||
|
||||
function enhanceConfiguredDropdowns() {
|
||||
[$("#v2RoleSelect"), $("#vehicleSelect"), $("#v2ExecutionSelect")]
|
||||
[$("#vehicleSelect")]
|
||||
.filter(Boolean)
|
||||
.forEach(enhanceSelect);
|
||||
}
|
||||
@@ -260,13 +255,6 @@
|
||||
<div class="v2-profile-copy">
|
||||
<small>WELCOME TO THE CABIN</small>
|
||||
<div class="v2-role-select">
|
||||
<span>Hi,</span>
|
||||
<select id="v2RoleSelect" aria-label="切换演示权限">
|
||||
<option value="user">user</option>
|
||||
<option value="admin">admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="v2-extension-profile-title" aria-label="自扩展面向所有登录用户">
|
||||
<span>Hi,</span><strong>user</strong>
|
||||
</div>
|
||||
</div>
|
||||
@@ -296,7 +284,6 @@
|
||||
<button type="button" id="v2LogoutButton" title="退出登录" aria-label="退出登录"><i data-lucide="log-out"></i></button>`);
|
||||
|
||||
commandPanel.append(profile, control, config, footer);
|
||||
$("#v2RoleSelect").value = ui.role;
|
||||
return $("#v2ConfigStack");
|
||||
}
|
||||
|
||||
@@ -355,21 +342,6 @@
|
||||
configStack.append(providerField);
|
||||
}
|
||||
|
||||
const executionSelect = make("div", "v2-config-field v2-execution-field", `
|
||||
<div class="v2-config-card v2-config-select-card">
|
||||
<i data-lucide="workflow"></i>
|
||||
<div class="v2-config-card-copy"><small>执行目标</small><div class="v2-config-card-select">
|
||||
<select id="v2ExecutionSelect" aria-label="选择执行目标">
|
||||
<option value="dry_run">虚拟 CAN</option>
|
||||
<option value="simulated_hardware">模拟台架</option>
|
||||
<option value="hardware">真实台架</option>
|
||||
</select>
|
||||
</div></div>
|
||||
<span class="v2-field-state">ADMIN</span>
|
||||
</div>
|
||||
<p class="provider-note" id="v2ExecutionNote">管理员可切换执行环境</p>`);
|
||||
configStack.append(executionSelect);
|
||||
|
||||
if (executionField) {
|
||||
executionField.classList.add("v2-native-execution-field");
|
||||
executionField.hidden = true;
|
||||
@@ -398,9 +370,9 @@
|
||||
const kicker = $(".section-kicker", panelHeading);
|
||||
const title = $("h2", panelHeading);
|
||||
const intro = $(".panel-intro", panelHeading);
|
||||
if (kicker) kicker.textContent = "SELF-ORCHESTRATION";
|
||||
if (title) title.textContent = "一句话,调好整车";
|
||||
if (intro) intro.textContent = "座椅、空调、天幕与氛围灯,一句话联动完成";
|
||||
if (kicker) kicker.textContent = "REMOTE ORCHESTRATION · LIVE SYNC";
|
||||
if (title) title.textContent = "一句话远程编排";
|
||||
if (intro) intro.textContent = "网页指令将通过同步链路发送至车机;车机任务与执行反馈在右侧实时更新";
|
||||
naturalPanel.append(panelHeading);
|
||||
}
|
||||
|
||||
@@ -428,7 +400,6 @@
|
||||
gestureDrawer.classList.add("v2-inline-gesture");
|
||||
const cameraStage = $(".gesture-camera-stage", gestureDrawer);
|
||||
const cameraToggle = $("#gestureStartButton", gestureDrawer);
|
||||
const debugToggle = $("#gestureArmButton", gestureDrawer);
|
||||
const actionRail = $(".gesture-actions", gestureDrawer);
|
||||
const statusStrip = $(".gesture-status-strip", gestureDrawer);
|
||||
const gestureNow = $(".gesture-now", gestureDrawer);
|
||||
@@ -438,9 +409,8 @@
|
||||
cameraToggle.setAttribute("aria-label", "开启摄像头");
|
||||
cameraToggle.setAttribute("title", "开启摄像头");
|
||||
}
|
||||
if (actionRail && cameraToggle && debugToggle) {
|
||||
if (actionRail && cameraToggle) {
|
||||
actionRail.classList.add("v2-gesture-action-rail");
|
||||
actionRail.insertBefore(debugToggle, cameraToggle);
|
||||
actionRail.append(cameraToggle);
|
||||
}
|
||||
|
||||
@@ -465,42 +435,17 @@
|
||||
}
|
||||
|
||||
function buildModuleSwitcher(mainPanel) {
|
||||
const modules = [
|
||||
{
|
||||
id: "self-calibration",
|
||||
label: "自标定",
|
||||
available: true,
|
||||
adminOnly: true,
|
||||
},
|
||||
{ id: "self-orchestration", label: "自编排", available: true },
|
||||
{ id: "self-extension", label: "自扩展", available: true },
|
||||
{
|
||||
id: "self-repair",
|
||||
label: "自修复",
|
||||
available: true,
|
||||
adminOnly: true,
|
||||
},
|
||||
{ id: "self-evolution", label: "自进化", available: true },
|
||||
];
|
||||
const switcher = make("nav", "v2-module-switcher", "");
|
||||
switcher.id = "v2ModuleSwitcher";
|
||||
switcher.setAttribute("role", "tablist");
|
||||
switcher.setAttribute("aria-label", "能力模块切换");
|
||||
modules.forEach((module, index) => {
|
||||
const button = make("button", "v2-module-tab", `<span>${module.label}</span>`);
|
||||
button.type = "button";
|
||||
button.id = `v2ModuleTab${index}`;
|
||||
button.dataset.module = module.id;
|
||||
button.setAttribute("role", "tab");
|
||||
button.setAttribute("aria-selected", "false");
|
||||
if (module.adminOnly) button.dataset.adminOnly = "true";
|
||||
if (!module.available) {
|
||||
button.disabled = true;
|
||||
button.setAttribute("aria-disabled", "true");
|
||||
button.title = module.adminOnly ? `${module.label}仅对 admin 开放` : `${module.label}模块待接入`;
|
||||
}
|
||||
switcher.append(button);
|
||||
});
|
||||
switcher.dataset.unifiedSync = "true";
|
||||
switcher.setAttribute("aria-label", "智能座舱远程控制与同步状态");
|
||||
switcher.innerHTML = `
|
||||
<div class="v2-unified-brand">
|
||||
<span class="v2-unified-brand-mark"><i data-lucide="waypoints"></i></span>
|
||||
<div><small>COCKPIT REMOTE · LIVE MIRROR</small><h1>511工程-智能座舱</h1></div>
|
||||
</div>
|
||||
<div class="v2-remote-control-state"><i data-lucide="radio-tower"></i><span>网页控制链路</span><strong>已启用</strong></div>
|
||||
<div class="v2-unified-live"><i></i><span>LIVE</span><b id="v2SyncClock">--:--:--</b></div>`;
|
||||
mainPanel.prepend(switcher);
|
||||
}
|
||||
|
||||
@@ -515,7 +460,7 @@
|
||||
<span class="v2-output-state"><i></i><span id="v2OutputState">LIVE STATE</span></span>
|
||||
</header>
|
||||
<div class="v2-output-content" id="v2OutputContent"></div>
|
||||
<nav class="v2-page-rail" id="v2PageRail" aria-label="管理员信息分页"></nav>`);
|
||||
<nav class="v2-page-rail" id="v2PageRail" aria-label="信息分页" hidden></nav>`);
|
||||
mainPanel.append(output);
|
||||
|
||||
const content = $("#v2OutputContent");
|
||||
@@ -543,9 +488,523 @@
|
||||
});
|
||||
}
|
||||
|
||||
const displaySync = {
|
||||
revision: -1,
|
||||
requestId: null,
|
||||
pollTimer: null,
|
||||
deviceFrame: null,
|
||||
activeDevice: null,
|
||||
pinnedDevice: null,
|
||||
logs: [],
|
||||
logKeys: new Set(),
|
||||
extensionFingerprint: "",
|
||||
};
|
||||
|
||||
const readText = (selector, fallback = "--") => $(selector)?.textContent?.trim() || fallback;
|
||||
|
||||
function seatDeviceRows(seat) {
|
||||
const card = $(`.seat-card[data-seat="${seat}"]`);
|
||||
if (!card) return [["状态", "等待反馈"]];
|
||||
const rows = [
|
||||
["反馈通道", $("[data-seat-feedback]", card)?.textContent?.trim() || "等待反馈"],
|
||||
...$$('[data-function]', card).map((item) => [
|
||||
item.textContent.replace(item.querySelector("b")?.textContent || "", "").trim(),
|
||||
$("b", item)?.textContent?.trim() || "--",
|
||||
]),
|
||||
...$$('.motion-axis', card).map((item) => [
|
||||
$("span", item)?.textContent?.trim() || "姿态",
|
||||
$("b", item)?.textContent?.trim() || "--",
|
||||
]),
|
||||
];
|
||||
return rows.filter(([label]) => label);
|
||||
}
|
||||
|
||||
const syncedDevices = [
|
||||
{
|
||||
id: "windows", name: "车窗", icon: "gallery-vertical-end",
|
||||
rows: () => [["设备状态", readText("#windowState", "等待反馈")], ["左前窗", readText("#frontLeftWindow")], ["右前窗", readText("#frontRightWindow")], ["左后窗", readText("#rearLeftWindow")], ["右后窗", readText("#rearRightWindow")]],
|
||||
},
|
||||
{
|
||||
id: "shade", name: "遮阳帘", icon: "panels-top-left",
|
||||
rows: () => [["设备状态", readText("#roofState", "等待反馈")], ["当前位置", readText("#shadePositionValue")], ["动作", readText("#shadeMotionState")], ["初始化", readText("#shadeInitializedState")], ["热保护", readText("#shadeThermalState")]],
|
||||
},
|
||||
{ id: "driver", name: "主驾座椅", icon: "armchair", rows: () => seatDeviceRows("driver") },
|
||||
{ id: "passenger", name: "副驾座椅", icon: "armchair", rows: () => seatDeviceRows("passenger") },
|
||||
{
|
||||
id: "reading", name: "阅读灯", icon: "book-open",
|
||||
rows: () => [["开关状态", readText("#interiorLightPower", "等待反馈")], ["控制通道", "CH1 · INTERIOR"], ["控制能力", "开 / 关"]],
|
||||
},
|
||||
{
|
||||
id: "ambient", name: "氛围灯", icon: "sparkles",
|
||||
rows: () => [["电源", readText("#ambientPower", "等待反馈")], ["模式", readText("#ambientMode")], ["前/后排亮度", readText("#ambientBrightness")], ["前/后排色号", readText("#ambientColorIndex")]],
|
||||
},
|
||||
{
|
||||
id: "headlight", name: "前大灯", icon: "car-front",
|
||||
rows: () => [["设备状态", readText("#frontLightState", "等待反馈")], ["近光灯", readText("#lowBeamState")], ["远光灯", readText("#highBeamState")], ["雾灯", readText("#fogLightState")], ["转向灯", readText("#turnSignalState")]],
|
||||
},
|
||||
{
|
||||
id: "climate", name: "空调", icon: "fan",
|
||||
rows: () => [["电源", readText("#climatePower", "等待反馈")], ["主驾温度", readText("#driverTemperature")], ["副驾温度", readText("#passengerTemperature")], ["风量", readText("#fanLevel")], ["主驾风向", readText("#driverAirDirection")], ["副驾风向", readText("#passengerAirDirection")], ["循环", readText("#cycleMode")], ["A/C", readText("#acState")], ["前除霜", readText("#defrostState")]],
|
||||
},
|
||||
];
|
||||
|
||||
function deviceSummary(device) {
|
||||
const values = device.rows().map(([, value]) => value).filter(Boolean);
|
||||
return values.slice(0, 3).join(" · ") || "等待反馈";
|
||||
}
|
||||
|
||||
function renderSyncedDevices() {
|
||||
const grid = $("#v2DeviceSyncGrid");
|
||||
if (!grid) return;
|
||||
grid.innerHTML = syncedDevices.map((device) => {
|
||||
const rows = device.rows();
|
||||
const detail = deviceSummary(device);
|
||||
const waiting = /--|等待|同步中|未知/.test(detail);
|
||||
return `<button class="v2-synced-device" type="button" data-device="${device.id}" data-ready="${String(!waiting)}" aria-label="查看${device.name}完整状态">
|
||||
<span><i data-lucide="${device.icon}"></i></span>
|
||||
<div><strong>${device.name}</strong><small>${escapeHtml(detail)}</small></div>
|
||||
<i class="v2-device-signal"></i>
|
||||
<dl>${rows.slice(0, 6).map(([label, value]) => `<div><dt>${escapeHtml(label)}</dt><dd>${escapeHtml(value)}</dd></div>`).join("")}</dl>
|
||||
</button>`;
|
||||
}).join("");
|
||||
if (displaySync.activeDevice) {
|
||||
$(`.v2-synced-device[data-device="${displaySync.activeDevice}"]`)?.classList.add("active");
|
||||
}
|
||||
window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } });
|
||||
}
|
||||
|
||||
function scheduleSyncedDeviceRender() {
|
||||
if (displaySync.deviceFrame) return;
|
||||
displaySync.deviceFrame = requestAnimationFrame(() => {
|
||||
displaySync.deviceFrame = null;
|
||||
renderSyncedDevices();
|
||||
if (displaySync.activeDevice) presentTwinDevice(displaySync.activeDevice, {
|
||||
pinned: Boolean(displaySync.pinnedDevice),
|
||||
selectSeat: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function appendSyncLog({ key = "", category = "sync", title, detail = "", status = "info" }) {
|
||||
if (!title || (key && displaySync.logKeys.has(key))) return;
|
||||
const item = {
|
||||
key,
|
||||
category,
|
||||
title,
|
||||
detail,
|
||||
status,
|
||||
time: new Intl.DateTimeFormat("zh-CN", {
|
||||
hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false,
|
||||
}).format(new Date()),
|
||||
};
|
||||
if (key) displaySync.logKeys.add(key);
|
||||
displaySync.logs.unshift(item);
|
||||
if (displaySync.logs.length > 40) {
|
||||
const removed = displaySync.logs.pop();
|
||||
if (removed?.key) displaySync.logKeys.delete(removed.key);
|
||||
}
|
||||
renderSyncLogs();
|
||||
}
|
||||
|
||||
function renderSyncLogs() {
|
||||
const list = $("#v2SyncLogList");
|
||||
if (!list) return;
|
||||
const filter = $("#v2LogFilter")?.value || "all";
|
||||
const items = displaySync.logs.filter((item) => filter === "all" || item.category === filter);
|
||||
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)}">
|
||||
<time>${escapeHtml(item.time)}</time>
|
||||
<span><i data-lucide="${icons[item.category] || "circle-dot"}"></i></span>
|
||||
<div><strong>${escapeHtml(item.title)}</strong><small>${escapeHtml(item.detail)}</small></div>
|
||||
</li>`).join("") : '<li class="v2-sync-log-empty"><div><strong>暂无记录</strong><small>车机任务、设备反馈与外设事件会显示在这里</small></div></li>';
|
||||
window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } });
|
||||
}
|
||||
|
||||
function panelVisible(key) {
|
||||
const panel = key === "devices" ? $("#v2DevicePanel") : $("#v2ExtensionPanel");
|
||||
if (panel) return !panel.classList.contains("is-collapsed");
|
||||
return sessionStorage.getItem(`cognitive-agent.sync-panel.${key}`) !== "hidden";
|
||||
}
|
||||
|
||||
function setSidePanelVisibility(key, visible) {
|
||||
const panel = key === "devices" ? $("#v2DevicePanel") : $("#v2ExtensionPanel");
|
||||
if (!panel) return;
|
||||
panel.hidden = false;
|
||||
panel.classList.toggle("is-collapsed", !visible);
|
||||
panel.dataset.collapsed = String(!visible);
|
||||
sessionStorage.setItem(`cognitive-agent.sync-panel.${key}`, visible ? "visible" : "hidden");
|
||||
$$(`[data-panel-toggle="${key}"], [data-panel-close="${key}"]`).forEach((button) => {
|
||||
button.classList.toggle("active", visible);
|
||||
button.setAttribute("aria-pressed", String(visible));
|
||||
button.setAttribute("aria-label", `${visible ? "收起" : "展开"}${key === "devices" ? "车端设备状态" : "外设与扩展能力"}`);
|
||||
const actionLabel = $("[data-panel-action-label]", button);
|
||||
if (actionLabel) actionLabel.textContent = visible ? "收起" : "展开";
|
||||
const stateLabel = $("[data-panel-state-label]", button);
|
||||
if (stateLabel) stateLabel.textContent = visible ? "已展开" : "已收起";
|
||||
});
|
||||
const openPanels = ["devices", "extensions"].filter((panelKey) => {
|
||||
const candidate = panelKey === "devices" ? $("#v2DevicePanel") : $("#v2ExtensionPanel");
|
||||
return candidate && !candidate.classList.contains("is-collapsed");
|
||||
});
|
||||
$(".joint-twin-shell")?.setAttribute("data-open-panels", openPanels.join(" ") || "none");
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
}
|
||||
|
||||
function presentTwinDevice(deviceId, { pinned = false, selectSeat = pinned } = {}) {
|
||||
const device = syncedDevices.find((item) => item.id === deviceId);
|
||||
const popover = $("#v2TwinDevicePopover");
|
||||
if (!device || !popover) return;
|
||||
displaySync.activeDevice = deviceId;
|
||||
if (pinned) displaySync.pinnedDevice = deviceId;
|
||||
const rows = device.rows();
|
||||
popover.hidden = false;
|
||||
popover.dataset.device = deviceId;
|
||||
popover.dataset.pinned = String(Boolean(displaySync.pinnedDevice));
|
||||
popover.innerHTML = `
|
||||
<header><span><i data-lucide="${device.icon}"></i></span><div><small>LIVE DEVICE FEEDBACK</small><strong>${escapeHtml(device.name)}</strong></div><b>${pinned ? "已固定" : "靠近查看"}</b></header>
|
||||
<dl>${rows.map(([label, value]) => `<div><dt>${escapeHtml(label)}</dt><dd>${escapeHtml(value)}</dd></div>`).join("")}</dl>`;
|
||||
$$("[data-twin-device]").forEach((button) => button.classList.toggle("active", button.dataset.twinDevice === deviceId));
|
||||
$$(".v2-synced-device").forEach((button) => button.classList.toggle("active", button.dataset.device === deviceId));
|
||||
if (selectSeat && ["driver", "passenger"].includes(deviceId)) {
|
||||
$(`[data-twin-seat="${deviceId}"]`)?.click();
|
||||
}
|
||||
window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } });
|
||||
}
|
||||
|
||||
function clearTwinDevicePreview({ force = false } = {}) {
|
||||
if (displaySync.pinnedDevice && !force) return;
|
||||
displaySync.activeDevice = null;
|
||||
if (force) displaySync.pinnedDevice = null;
|
||||
const popover = $("#v2TwinDevicePopover");
|
||||
if (popover) popover.hidden = true;
|
||||
$$("[data-twin-device], .v2-synced-device").forEach((button) => button.classList.remove("active"));
|
||||
}
|
||||
|
||||
function buildTwinDeviceInteractions() {
|
||||
const stage = $("#seatTwin");
|
||||
if (!stage || $("#v2TwinHotspots")) return;
|
||||
const hotspots = make("div", "v2-twin-hotspots", syncedDevices.map((device) => `
|
||||
<button type="button" data-twin-device="${device.id}" aria-label="查看${device.name}状态"><i data-lucide="${device.icon}"></i><span>${device.name}</span></button>`).join(""));
|
||||
hotspots.id = "v2TwinHotspots";
|
||||
const popover = make("aside", "v2-twin-device-popover", "");
|
||||
popover.id = "v2TwinDevicePopover";
|
||||
popover.hidden = true;
|
||||
popover.setAttribute("aria-live", "polite");
|
||||
stage.append(hotspots, popover);
|
||||
|
||||
hotspots.addEventListener("pointerdown", (event) => event.stopPropagation());
|
||||
hotspots.addEventListener("pointerover", (event) => {
|
||||
const button = event.target.closest("[data-twin-device]");
|
||||
if (button && !displaySync.pinnedDevice) presentTwinDevice(button.dataset.twinDevice);
|
||||
});
|
||||
hotspots.addEventListener("focusin", (event) => {
|
||||
const button = event.target.closest("[data-twin-device]");
|
||||
if (button) presentTwinDevice(button.dataset.twinDevice);
|
||||
});
|
||||
hotspots.addEventListener("pointerout", (event) => {
|
||||
if (event.relatedTarget?.closest?.("#v2TwinDevicePopover")) return;
|
||||
window.setTimeout(() => clearTwinDevicePreview(), 100);
|
||||
});
|
||||
hotspots.addEventListener("click", (event) => {
|
||||
const button = event.target.closest("[data-twin-device]");
|
||||
if (!button) return;
|
||||
event.stopPropagation();
|
||||
const samePinned = displaySync.pinnedDevice === button.dataset.twinDevice;
|
||||
if (samePinned) clearTwinDevicePreview({ force: true });
|
||||
else presentTwinDevice(button.dataset.twinDevice, { pinned: true });
|
||||
});
|
||||
popover.addEventListener("pointerdown", (event) => event.stopPropagation());
|
||||
window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } });
|
||||
}
|
||||
|
||||
function taskKind(utterance, result, source = "") {
|
||||
if (source === "web_console") return "网页远程编排";
|
||||
if (result?.mode_profile) return "自定义功能模式";
|
||||
const preset = $$(".mode-chips [data-mode-command]").find((button) => (
|
||||
String(utterance).includes(button.textContent.trim())
|
||||
|| button.dataset.modeCommand === utterance
|
||||
));
|
||||
return preset ? "预设功能模式" : utterance ? "一句话自定义编排" : "等待编排任务";
|
||||
}
|
||||
|
||||
function renderTaskTimeline(task) {
|
||||
const timeline = $("#v2TaskTimeline");
|
||||
if (!timeline) return;
|
||||
const result = task?.result;
|
||||
const trace = Array.isArray(result?.trace_events) ? result.trace_events : [];
|
||||
const domains = Object.keys(result?.function_configs || {});
|
||||
const stages = task?.status === "running"
|
||||
? ["接收编排指令", "解析意图", "构建功能模式", "等待设备反馈"]
|
||||
: trace.length
|
||||
? trace.slice(-4).map((item) => item.label || item.stage || item.event || "执行步骤")
|
||||
: domains.length
|
||||
? ["接收编排指令", `编排 ${domains.length} 个设备域`, "下发执行计划", "同步执行结果"]
|
||||
: ["等待车机或网页端下发编排任务"];
|
||||
timeline.innerHTML = stages.map((stage, index) => `
|
||||
<span class="${task?.status === "running" && index === stages.length - 1 ? "active" : "done"}">
|
||||
<i>${String(index + 1).padStart(2, "0")}</i><b>${escapeHtml(stage)}</b>
|
||||
</span>`).join("");
|
||||
}
|
||||
|
||||
function setTaskPresentation(task = {}) {
|
||||
const utterance = String(task.utterance || "");
|
||||
const result = task.result && typeof task.result === "object" ? task.result : null;
|
||||
const input = $("#utterance");
|
||||
if (input && utterance && document.activeElement !== input) input.value = utterance;
|
||||
const kind = taskKind(utterance, result, String(task.source || ""));
|
||||
if ($("#v2TaskKind")) $("#v2TaskKind").textContent = kind;
|
||||
if ($("#v2TaskCommand")) $("#v2TaskCommand").textContent = utterance || "等待车机端或网页端选择预设模式、输入一句话指令";
|
||||
const normalizedStatus = String(task.status || "idle").toLowerCase();
|
||||
const statusMap = {
|
||||
idle: ["等待任务", "idle"],
|
||||
running: ["构建 / 执行中", "running"],
|
||||
succeeded: ["执行完成", "succeeded"],
|
||||
partially_succeeded: ["部分完成", "partial"],
|
||||
needs_confirmation: ["等待确认", "partial"],
|
||||
failed: ["执行失败", "failed"],
|
||||
};
|
||||
const [label, state] = statusMap[normalizedStatus] || statusMap.idle;
|
||||
const badge = $("#v2TaskState");
|
||||
if (badge) {
|
||||
badge.textContent = label;
|
||||
badge.dataset.state = state;
|
||||
}
|
||||
$$(".mode-chips [data-mode-command]").forEach((button) => {
|
||||
const active = Boolean(utterance) && (
|
||||
utterance === button.dataset.modeCommand
|
||||
|| utterance.includes(button.textContent.trim())
|
||||
);
|
||||
button.classList.toggle("v2-sync-active", active);
|
||||
});
|
||||
renderTaskTimeline(task);
|
||||
}
|
||||
|
||||
function applyDisplaySnapshot(snapshot) {
|
||||
const health = snapshot?.extension_health || {};
|
||||
const physicallyConnected = health.transport !== "simulation"
|
||||
&& Boolean(health.connected || health.peer_connected);
|
||||
const synchronizedCatalog = Array.isArray(snapshot?.extension?.catalog)
|
||||
? snapshot.extension.catalog
|
||||
: [];
|
||||
const connected = physicallyConnected || synchronizedCatalog.length > 0;
|
||||
const listening = Boolean(health.listening);
|
||||
const usbState = $("#v2UsbState");
|
||||
if (usbState) {
|
||||
const usbLabel = $("span", usbState);
|
||||
if (usbLabel) usbLabel.textContent = physicallyConnected
|
||||
? "USB 外设已接入"
|
||||
: synchronizedCatalog.length
|
||||
? `${new Set(synchronizedCatalog.map((item) => item.device_id)).size} 个外设已同步`
|
||||
: listening ? "等待 USB 外设" : "扩展链路检查中";
|
||||
usbState.dataset.state = connected ? "online" : listening ? "listening" : "offline";
|
||||
}
|
||||
if ($("#v2SyncTransport")) {
|
||||
$("#v2SyncTransport").textContent = `${String(health.transport || "GSCU").toUpperCase()} · ${health.peer || health.listen || "等待连接"}`;
|
||||
}
|
||||
const vehicleOnline = health.vehicle?.connected !== false;
|
||||
const vehicleBadge = $("#v2HeadUnitState");
|
||||
if (vehicleBadge) {
|
||||
vehicleBadge.dataset.state = vehicleOnline ? "online" : "offline";
|
||||
$("span", vehicleBadge).textContent = vehicleOnline ? "车机链路在线" : "车机链路离线";
|
||||
}
|
||||
const revision = Number(snapshot?.revision ?? -1);
|
||||
if (revision !== displaySync.revision) {
|
||||
displaySync.revision = revision;
|
||||
const task = snapshot?.task || {};
|
||||
setTaskPresentation(task);
|
||||
const sourceLabel = task.source === "web_console" ? "网页端" : "车机端";
|
||||
if (task.utterance) {
|
||||
appendSyncLog({
|
||||
key: `task:${revision}:${task.status}`,
|
||||
category: "task",
|
||||
title: `${sourceLabel}${task.status === "running" ? "下发编排任务" : "任务状态已同步"}`,
|
||||
detail: `${task.utterance} · ${task.status || "idle"}`,
|
||||
status: ["succeeded", "partially_succeeded"].includes(task.status) ? "success" : task.status === "failed" ? "error" : "info",
|
||||
});
|
||||
}
|
||||
const result = task.result;
|
||||
const requestId = result?.request_id || null;
|
||||
if (result && requestId !== displaySync.requestId) {
|
||||
displaySync.requestId = requestId;
|
||||
window.CognitiveAgentConsole?.applySyncedResult?.(result, {
|
||||
utterance: task.utterance || "",
|
||||
});
|
||||
}
|
||||
}
|
||||
const extensionFingerprint = synchronizedCatalog
|
||||
.map((item) => `${item.device_id || "external"}:${item.action || item.name || "capability"}`)
|
||||
.sort()
|
||||
.join("|");
|
||||
if (extensionFingerprint && extensionFingerprint !== displaySync.extensionFingerprint) {
|
||||
displaySync.extensionFingerprint = extensionFingerprint;
|
||||
appendSyncLog({
|
||||
key: `extension:${extensionFingerprint}`,
|
||||
category: "extension",
|
||||
title: "外设能力目录已同步",
|
||||
detail: `${new Set(synchronizedCatalog.map((item) => item.device_id || "external")).size} 个外设 · ${synchronizedCatalog.length} 项能力`,
|
||||
status: "success",
|
||||
});
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent("cognitive-agent:display-sync", { detail: snapshot }));
|
||||
}
|
||||
|
||||
async function pollVehicleDisplaySync() {
|
||||
try {
|
||||
const response = await fetch("/api/vehicle-display-sync", {
|
||||
credentials: "same-origin",
|
||||
cache: "no-store",
|
||||
headers: { Accept: "application/json" },
|
||||
});
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
applyDisplaySnapshot(await response.json());
|
||||
$("#v2MirrorState")?.setAttribute("data-state", "online");
|
||||
if ($("#v2MirrorState span")) $("#v2MirrorState span").textContent = "同步正常";
|
||||
appendSyncLog({ key: "sync:online", category: "sync", title: "网页同步链路已建立", detail: "车机任务与设备状态每 2 秒同步", status: "success" });
|
||||
} catch (_error) {
|
||||
console.warn("[vehicle-display-sync]", _error);
|
||||
$("#v2MirrorState")?.setAttribute("data-state", "offline");
|
||||
if ($("#v2MirrorState span")) $("#v2MirrorState span").textContent = "等待同步服务";
|
||||
}
|
||||
}
|
||||
|
||||
function enableRemoteOrchestration() {
|
||||
const form = $("#requestForm");
|
||||
const input = $("#utterance");
|
||||
if (input) {
|
||||
input.readOnly = false;
|
||||
input.removeAttribute("aria-readonly");
|
||||
input.setAttribute("aria-label", "输入一句话远程编排车端功能");
|
||||
input.placeholder = "例如:副驾调到舒适姿态,空调 24℃,氛围灯改成暖橙色";
|
||||
}
|
||||
form?.addEventListener("submit", () => {
|
||||
const utterance = input?.value.trim();
|
||||
if (utterance) appendSyncLog({
|
||||
category: "task",
|
||||
title: "网页端发送编排指令",
|
||||
detail: utterance,
|
||||
status: "info",
|
||||
});
|
||||
});
|
||||
$$("[data-mode-command]").forEach((button) => {
|
||||
button.removeAttribute("aria-disabled");
|
||||
button.title = "点击后由网页端发送至车机执行";
|
||||
});
|
||||
}
|
||||
|
||||
function buildSyncControlLayer() {
|
||||
document.body.classList.add("v2-passive-display", "v2-interactive-sync");
|
||||
document.body.dataset.module = "self-orchestration";
|
||||
enableRemoteOrchestration();
|
||||
|
||||
const sidebar = $(".v2-sidebar");
|
||||
const sidebarFooter = $(".v2-sidebar-footer", sidebar);
|
||||
const syncSidebar = make("section", "v2-sync-sidebar", `
|
||||
<div class="v2-sync-sidebar-heading"><small>REMOTE CLIENT</small><h2>511工程-智能座舱</h2><p>网页可远程编排,车机任务、设备反馈与 USB 外设能力保持同步。</p></div>
|
||||
<div class="v2-sync-link-stack">
|
||||
<div class="v2-sync-link" id="v2HeadUnitState" data-state="checking"><i data-lucide="monitor-smartphone"></i><div><strong>车机链路</strong><span>连接检查中</span></div><b></b></div>
|
||||
<div class="v2-sync-link" id="v2MirrorState" data-state="checking"><i data-lucide="refresh-cw"></i><div><strong>网页同步</strong><span>建立同步中</span></div><b></b></div>
|
||||
<div class="v2-sync-link" id="v2UsbState" data-state="checking"><i data-lucide="usb"></i><div><strong>USB 扩展</strong><span>检查外设中</span></div><b></b></div>
|
||||
</div>
|
||||
<section class="v2-sync-log" aria-labelledby="v2SyncLogTitle">
|
||||
<header><div><small>RUNTIME / SYNC</small><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>
|
||||
<div class="v2-sync-protocol"><i data-lucide="radio-tower"></i><div><small>扩展链路</small><strong id="v2SyncTransport">GSCU · 等待连接</strong></div></div>`);
|
||||
if (sidebar && sidebarFooter) sidebar.insertBefore(syncSidebar, sidebarFooter);
|
||||
|
||||
const naturalPanel = $("#v2NaturalPanel");
|
||||
const taskOverview = make("section", "v2-task-overview", `
|
||||
<div class="v2-task-copy"><span id="v2TaskKind">当前车机任务</span><strong id="v2TaskCommand">等待车机端或网页端选择预设模式、输入一句话指令</strong></div>
|
||||
<span class="v2-task-state" id="v2TaskState" data-state="idle">等待任务</span>
|
||||
<div class="v2-task-timeline" id="v2TaskTimeline"><span><i>01</i><b>等待车机或网页端下发编排任务</b></span></div>`);
|
||||
naturalPanel?.append(taskOverview);
|
||||
|
||||
const jointShell = $(".joint-twin-shell");
|
||||
const cabinBand = $(".cabin-band", jointShell);
|
||||
if (jointShell && cabinBand) {
|
||||
const rightRail = make("aside", "v2-unified-right-rail", `
|
||||
<section class="v2-device-sync-board v2-side-status-panel" id="v2DevicePanel">
|
||||
<header><div><small>LIVE VEHICLE DEVICES</small><h3>车端设备状态</h3></div><span><i></i>实时反馈</span><button type="button" data-panel-toggle="devices" aria-label="收起车端设备状态"><i data-lucide="panel-left-close"></i><span data-panel-action-label>收起</span></button></header>
|
||||
<div class="v2-device-sync-grid" id="v2DeviceSyncGrid"></div>
|
||||
</section>
|
||||
<section class="v2-extension-panel v2-side-status-panel" id="v2ExtensionPanel"><div class="v2-extension-sync-mount" id="vehicleExtensionSync" aria-live="polite"></div></section>`);
|
||||
jointShell.insertBefore(rightRail, cabinBand);
|
||||
const panelControls = make("div", "v2-panel-visibility-controls", `
|
||||
<button type="button" data-panel-toggle="devices" aria-pressed="true"><i data-lucide="sliders-horizontal"></i><span>设备状态</span><b data-panel-state-label>已展开</b></button>
|
||||
<button type="button" data-panel-toggle="extensions" aria-pressed="true"><i data-lucide="usb"></i><span>外设能力</span><b data-panel-state-label>已展开</b></button>`);
|
||||
$(".twin-toolbar", jointShell)?.prepend(panelControls);
|
||||
buildTwinDeviceInteractions();
|
||||
cabinBand.classList.add("v2-source-state-only");
|
||||
new MutationObserver(scheduleSyncedDeviceRender).observe(cabinBand, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
characterData: true,
|
||||
subtree: true,
|
||||
});
|
||||
const stateBand = $(".state-band");
|
||||
if (stateBand) new MutationObserver(scheduleSyncedDeviceRender).observe(stateBand, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
characterData: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
setSidePanelVisibility("devices", panelVisible("devices"));
|
||||
setSidePanelVisibility("extensions", panelVisible("extensions"));
|
||||
document.addEventListener("click", (event) => {
|
||||
const toggle = event.target.closest("[data-panel-toggle], [data-panel-close]");
|
||||
if (toggle) {
|
||||
const key = toggle.dataset.panelToggle || toggle.dataset.panelClose;
|
||||
setSidePanelVisibility(key, !panelVisible(key));
|
||||
}
|
||||
});
|
||||
$("#v2DeviceSyncGrid")?.addEventListener("click", (event) => {
|
||||
const card = event.target.closest("[data-device]");
|
||||
if (!card) return;
|
||||
presentTwinDevice(card.dataset.device, { pinned: true });
|
||||
});
|
||||
$("#v2LogFilter")?.addEventListener("change", renderSyncLogs);
|
||||
window.addEventListener("cognitive-agent:result", (event) => {
|
||||
const result = event.detail || {};
|
||||
appendSyncLog({
|
||||
key: result.request_id ? `result:${result.request_id}` : "",
|
||||
category: "task",
|
||||
title: ["succeeded", "partially_succeeded"].includes(result.status) ? "编排任务执行完成" : "编排任务返回结果",
|
||||
detail: `${result.summary || result.message || "执行反馈已同步"} · ${result.status || "unknown"}`,
|
||||
status: ["succeeded", "partially_succeeded"].includes(result.status) ? "success" : result.status === "failed" ? "error" : "info",
|
||||
});
|
||||
});
|
||||
window.addEventListener("cognitive-agent:request-complete", (event) => {
|
||||
if (!event.detail?.error) return;
|
||||
appendSyncLog({ category: "task", title: "编排任务执行失败", detail: event.detail.error.message || "请求失败", status: "error" });
|
||||
});
|
||||
window.addEventListener("cognitive-agent:connection", (event) => {
|
||||
const online = event.detail?.status === "online";
|
||||
appendSyncLog({
|
||||
key: `connection:${event.detail?.status}`,
|
||||
category: "sync",
|
||||
title: online ? "车机服务连接正常" : "车机服务连接异常",
|
||||
detail: online ? "远程控制与状态同步通道可用" : String(event.detail?.status || "offline"),
|
||||
status: online ? "success" : "error",
|
||||
});
|
||||
});
|
||||
renderSyncedDevices();
|
||||
setTaskPresentation({ status: "idle" });
|
||||
appendSyncLog({ key: "client:ready", category: "sync", title: "同步控制台已就绪", detail: "正在连接车机任务与设备状态服务", status: "info" });
|
||||
pollVehicleDisplaySync();
|
||||
displaySync.pollTimer = window.setInterval(pollVehicleDisplaySync, 2000);
|
||||
const updateClock = () => {
|
||||
if ($("#v2SyncClock")) $("#v2SyncClock").textContent = new Intl.DateTimeFormat("zh-CN", {
|
||||
hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false,
|
||||
}).format(new Date());
|
||||
};
|
||||
updateClock();
|
||||
window.setInterval(updateClock, 1000);
|
||||
}
|
||||
|
||||
function setPage(pageIndex) {
|
||||
const maxPage = ui.role === "admin" ? pageDefinitions.length - 1 : 0;
|
||||
ui.activePage = Math.max(0, Math.min(Number(pageIndex) || 0, maxPage));
|
||||
ui.activePage = 0;
|
||||
const definition = pageDefinitions[ui.activePage];
|
||||
$(".v2-output-shell")?.classList.toggle("v2-twin-active", definition.index === 0);
|
||||
$$(".v2-output-page").forEach((page) => {
|
||||
@@ -654,128 +1113,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
function syncSidebarControlForModule(evolutionView = window.CognitiveAgentSelfEvolution?.getView?.()) {
|
||||
const evolutionMode = ui.activeModule === "self-evolution";
|
||||
const caption = $(".v2-control-section > .v2-field-caption");
|
||||
if (caption) caption.textContent = evolutionMode ? "进化视图" : "控制方式";
|
||||
const definitions = evolutionMode
|
||||
? [
|
||||
{ label: "座椅称重进化", icon: "scale", active: evolutionView !== "system" },
|
||||
{ label: "系统能力日志", icon: "scroll-text", active: evolutionView === "system" },
|
||||
]
|
||||
: [
|
||||
{ label: "自然语言控制", icon: "message-square-text", active: ui.controlMode === "natural" },
|
||||
{ label: "手势控制", icon: "scan-face", active: ui.controlMode === "gesture" },
|
||||
];
|
||||
$$('[data-v2-control]').forEach((button, index) => {
|
||||
const definition = definitions[index];
|
||||
if (!definition) return;
|
||||
const label = $("span", button);
|
||||
const icon = $("svg, i", button);
|
||||
if (label) label.textContent = definition.label;
|
||||
if (icon) icon.setAttribute("data-lucide", definition.icon);
|
||||
button.classList.toggle("active", definition.active);
|
||||
button.setAttribute("aria-pressed", String(definition.active));
|
||||
});
|
||||
const switcher = $(".v2-control-switch");
|
||||
switcher?.setAttribute("aria-label", evolutionMode ? "自进化视图" : "控制方式");
|
||||
window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } });
|
||||
}
|
||||
|
||||
function setModule(module, { persist = true } = {}) {
|
||||
if (["self-calibration", "self-repair"].includes(module) && ui.role !== "admin") {
|
||||
setRole("admin", { allowModuleTransition: true });
|
||||
}
|
||||
const allowed = new Set(["self-orchestration", "self-extension", "self-evolution"]);
|
||||
if (ui.role === "admin") {
|
||||
allowed.add("self-calibration");
|
||||
allowed.add("self-repair");
|
||||
}
|
||||
const allowed = new Set(["self-orchestration", "self-extension"]);
|
||||
ui.activeModule = allowed.has(module) ? module : "self-orchestration";
|
||||
if (ui.activeModule === "self-extension" && ui.role !== "user") {
|
||||
setRole("user");
|
||||
}
|
||||
if (persist) {
|
||||
sessionStorage.setItem("cognitive-agent.active-module", ui.activeModule);
|
||||
}
|
||||
document.body.dataset.module = ui.activeModule;
|
||||
const roleSelect = $("#v2RoleSelect");
|
||||
if (roleSelect) {
|
||||
roleSelect.disabled = ui.activeModule === "self-repair";
|
||||
dropdownControls.get(roleSelect)?.sync();
|
||||
}
|
||||
$$('.v2-module-tab').forEach((button) => {
|
||||
const active = button.dataset.module === ui.activeModule;
|
||||
button.classList.toggle("active", active);
|
||||
button.setAttribute("aria-selected", String(active));
|
||||
button.tabIndex = active ? 0 : -1;
|
||||
});
|
||||
if (["self-extension", "self-calibration", "self-repair", "self-evolution"].includes(ui.activeModule)) {
|
||||
if (ui.activeModule === "self-extension") {
|
||||
const drawer = $("#gestureDrawer");
|
||||
if (drawer?.classList.contains("open")) $("#gestureCloseButton")?.click();
|
||||
} else {
|
||||
setControlMode(ui.controlMode, { triggerGesture: false });
|
||||
}
|
||||
syncSidebarControlForModule();
|
||||
window.dispatchEvent(new CustomEvent("cognitive-agent:module-change", {
|
||||
detail: { module: ui.activeModule },
|
||||
}));
|
||||
window.setTimeout(() => window.dispatchEvent(new Event("resize")), 120);
|
||||
}
|
||||
|
||||
function setRole(role, { allowModuleTransition = false } = {}) {
|
||||
if (ui.activeModule === "self-extension" && role !== "user" && !allowModuleTransition) {
|
||||
role = "user";
|
||||
}
|
||||
if (ui.activeModule === "self-repair" && role !== "admin") {
|
||||
const roleSelect = $("#v2RoleSelect");
|
||||
if (roleSelect) {
|
||||
roleSelect.value = "admin";
|
||||
dropdownControls.get(roleSelect)?.sync();
|
||||
}
|
||||
return;
|
||||
}
|
||||
ui.role = role === "admin" ? "admin" : "user";
|
||||
sessionStorage.setItem("cognitive-agent.demo-role", ui.role);
|
||||
document.body.dataset.demoRole = ui.role;
|
||||
window.dispatchEvent(new CustomEvent("cognitive-agent:demo-role-change", {
|
||||
detail: { role: ui.role },
|
||||
}));
|
||||
$("#v2RoleSelect").value = ui.role;
|
||||
dropdownControls.get($("#v2RoleSelect"))?.sync();
|
||||
$("#v2PageRail").hidden = ui.role !== "admin";
|
||||
const calibrationTab = $('.v2-module-tab[data-module="self-calibration"]');
|
||||
if (calibrationTab) {
|
||||
calibrationTab.disabled = false;
|
||||
calibrationTab.setAttribute("aria-disabled", "false");
|
||||
calibrationTab.title = ui.role === "admin"
|
||||
? ""
|
||||
: "进入自标定时将自动切换为 admin";
|
||||
}
|
||||
const repairTab = $('.v2-module-tab[data-module="self-repair"]');
|
||||
if (repairTab) {
|
||||
repairTab.hidden = false;
|
||||
repairTab.disabled = false;
|
||||
repairTab.setAttribute("aria-disabled", "false");
|
||||
repairTab.title = ui.role === "admin"
|
||||
? ""
|
||||
: "进入自修复时将自动切换为 admin";
|
||||
}
|
||||
if (ui.role !== "admin") {
|
||||
if (["self-calibration", "self-repair"].includes(ui.activeModule)) {
|
||||
setModule("self-orchestration");
|
||||
}
|
||||
setPage(0);
|
||||
requestUserSafeExecutionMode();
|
||||
} else {
|
||||
const note = $("#v2ExecutionNote");
|
||||
if (note) note.textContent = "管理员可切换虚拟 CAN、模拟台架与真实台架";
|
||||
const runButton = $("#runButton");
|
||||
if (runButton && !runButton.classList.contains("loading")) runButton.disabled = false;
|
||||
syncExecutionSelector();
|
||||
}
|
||||
}
|
||||
|
||||
function setSidebarCollapsed(collapsed) {
|
||||
ui.sidebarCollapsed = Boolean(collapsed);
|
||||
sessionStorage.setItem(
|
||||
@@ -804,7 +1166,7 @@
|
||||
const runButton = $("#runButton");
|
||||
if (!dryRunButton) return;
|
||||
if (!dryRunButton.classList.contains("active")) dryRunButton.click();
|
||||
if (note) note.textContent = "测试用户使用虚拟 CAN;真实台架仅由管理员显式启用";
|
||||
if (note) note.textContent = "user 固定使用虚拟 CAN 安全执行环境";
|
||||
if (userTarget) userTarget.dataset.state = "ready";
|
||||
if (userTargetText) userTargetText.textContent = "执行目标:虚拟 CAN";
|
||||
if (runButton && !runButton.classList.contains("loading")) runButton.disabled = false;
|
||||
@@ -847,7 +1209,7 @@
|
||||
$(".v2-sidebar-footer")?.classList.toggle("offline", !online);
|
||||
}
|
||||
syncExecutionSelector();
|
||||
if (ui.role === "user") requestUserSafeExecutionMode();
|
||||
requestUserSafeExecutionMode();
|
||||
}
|
||||
|
||||
function setVehicleLinkState(connected) {
|
||||
@@ -877,21 +1239,11 @@
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
$("#v2RoleSelect")?.addEventListener("change", (event) => setRole(event.target.value));
|
||||
$$('[data-v2-control]').forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
if (ui.activeModule === "self-evolution") {
|
||||
const view = button.dataset.v2Control === "gesture" ? "system" : "weighing";
|
||||
window.CognitiveAgentSelfEvolution?.setView?.(view);
|
||||
syncSidebarControlForModule(view);
|
||||
return;
|
||||
}
|
||||
setControlMode(button.dataset.v2Control);
|
||||
});
|
||||
});
|
||||
window.addEventListener("cognitive-agent:evolution-view-change", (event) => {
|
||||
if (ui.activeModule === "self-evolution") syncSidebarControlForModule(event.detail?.view);
|
||||
});
|
||||
$$('.v2-module-tab').forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
if (!button.disabled) setModule(button.dataset.module);
|
||||
@@ -932,6 +1284,16 @@
|
||||
() => requestAnimationFrame(updateModeEdgeHints),
|
||||
{ passive: true },
|
||||
);
|
||||
modeChips?.addEventListener("wheel", (event) => {
|
||||
if (modeChips.scrollWidth <= modeChips.clientWidth + 2) return;
|
||||
const distance = Math.abs(event.deltaX) > Math.abs(event.deltaY)
|
||||
? event.deltaX
|
||||
: event.deltaY;
|
||||
if (!distance) return;
|
||||
event.preventDefault();
|
||||
modeChips.scrollBy({ left: distance, behavior: "smooth" });
|
||||
window.requestAnimationFrame(updateModeEdgeHints);
|
||||
}, { passive: false });
|
||||
if (modeChips) {
|
||||
new MutationObserver(() => requestAnimationFrame(updateModeEdgeHints))
|
||||
.observe(modeChips, { childList: true });
|
||||
@@ -990,16 +1352,14 @@
|
||||
gestureDrawer,
|
||||
);
|
||||
buildModuleSwitcher(mainPanel);
|
||||
window.CognitiveAgentSelfEvolution?.mount(mainPanel);
|
||||
|
||||
$(".pipeline", mainPanel)?.classList.add("v2-legacy-hidden");
|
||||
$(".topbar")?.classList.add("v2-legacy-hidden");
|
||||
$(".gesture-backdrop")?.classList.add("v2-legacy-hidden");
|
||||
buildOutput(mainPanel);
|
||||
buildSyncControlLayer();
|
||||
bindEvents();
|
||||
setControlMode(ui.controlMode, { triggerGesture: false });
|
||||
setModule(ui.activeModule, { persist: false });
|
||||
setRole(ui.role);
|
||||
setSidebarCollapsed(ui.sidebarCollapsed);
|
||||
setPage(0);
|
||||
syncRuntimePresentation();
|
||||
@@ -1014,7 +1374,7 @@
|
||||
window.CognitiveAgentConsoleV2 = {
|
||||
enhanceSelect,
|
||||
getModule: () => ui.activeModule,
|
||||
getRole: () => ui.role,
|
||||
getRole: () => "user",
|
||||
setModule,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user