优化前端,优化前端指令,优化自定义控制

This commit is contained in:
minghanchen
2026-08-24 09:40:16 +08:00
parent fff7657fea
commit 8d05815773
130 changed files with 5543 additions and 2206 deletions
+367 -330
View File
@@ -20,7 +20,6 @@
sidebarCollapsed: sessionStorage.getItem("cognitive-agent.sidebar-collapsed") === "true",
};
const dropdownControls = new Map();
let reasoningPickerControl = null;
function make(tag, className, html) {
@@ -39,10 +38,261 @@
.replaceAll("'", "'");
}
function closeCustomDropdowns(except = null) {
dropdownControls.forEach((control) => {
if (control !== except) control.close();
const normalizedCanTarget = (target = {}) => String(
target.zone || target.instance || target.device || "vehicle",
).replaceAll("_", "-");
const normalizedOnOff = (value) => {
if ([true, 1, "1", "on", "open", "enabled"].includes(value)) return "on";
if ([false, 0, "0", "off", "close", "disabled"].includes(value)) return "off";
return null;
};
const boundedInteger = (value, minimum, maximum) => {
const numeric = Number(value);
return Number.isInteger(numeric) && numeric >= minimum && numeric <= maximum
? numeric
: null;
};
function parsedCanCommandForOperation(operation = {}) {
const capability = String(operation.capability || "");
const parameters = operation.parameters || {};
const target = normalizedCanTarget(operation.target);
const onOff = normalizedOnOff(parameters.state ?? parameters.power);
const hyphenated = (value) => String(value || "").replaceAll("_", "-");
const command = (category, text, channel = "CAN1", frame = "") => ({
channel, category, command: text, ...(frame ? { frame } : {}),
});
const hexByte = (value) => Number(value).toString(16).padStart(2, "0").toUpperCase();
const otaCan0Frame = (opcode, selector, argument) => {
const numeric = Number(argument) >>> 0;
const argumentBytes = [0, 8, 16, 24].map((shift) => hexByte((numeric >>> shift) & 0xFF));
return `0x6A0 · C7 <SEQ> ${hexByte(opcode)} ${hexByte(selector)} ${argumentBytes.join(" ")}`;
};
const isPassengerSeat = new Set(["passenger", "front-right"]).has(target);
if (isPassengerSeat) {
const passengerAxes = {
"seat.set_longitudinal_position": ["副驾座椅前后调节", "longitudinal", 0],
"seat.set_cushion_tilt": ["副驾座椅高度调节", "height", 1],
"seat.set_backrest_recline": ["副驾座椅靠背调节", "backrest", 2],
"seat.set_legrest_height": ["副驾座椅腿托调节", "legrest", 3],
};
const axis = passengerAxes[capability];
const position = boundedInteger(parameters.position_pct, 0, 100);
if (axis && position !== null) {
const targetPermille = position * 10;
return command(
axis[0],
`seat passenger ${axis[1]} position ${position}`,
"CAN0",
otaCan0Frame(0x64, axis[2], targetPermille),
);
}
if (capability === "seat.start_dynamic_mode") {
const mode = boundedInteger(parameters.mode_id, 1, 2);
if (mode !== null) {
const dynamic = mode === 1
? { name: "rocking-horse", selector: 2 }
: { name: "cradle", selector: 1 };
return command(
"副驾座椅动态模式",
`seat passenger dynamic start ${dynamic.name}`,
"CAN0",
otaCan0Frame(0x66, dynamic.selector, 300),
);
}
}
if (capability === "seat.stop_dynamic_mode") {
return command(
"副驾座椅动态模式",
"seat passenger dynamic stop",
"CAN0",
otaCan0Frame(0x63, 0xFF, 0),
);
}
}
if (capability === "door.set_rear_lock") {
const lockState = String(parameters.state || "");
const action = new Set(["lock", "unlock"]).has(lockState)
? lockState
: onOff === "on" ? "lock" : onOff === "off" ? "unlock" : null;
if (action) return command("后排门锁", `door rear ${action}`);
}
if (capability === "door.set_vehicle_lock") {
const lockState = String(parameters.state || "");
if (new Set(["lock", "unlock"]).has(lockState)) {
return command("整车门锁", `door vehicle ${lockState}`);
}
}
if (capability === "window.set_motion") {
const scope = new Set(["all", "front", "rear", "front-left", "front-right", "rear-left", "rear-right"]);
const motion = String(parameters.motion || parameters.action || "");
if (scope.has(target) && new Set(["open", "close", "stop"]).has(motion)) {
return command("车窗动作", `window ${target} ${motion}`);
}
}
if (capability === "window.set_position") {
const position = boundedInteger(parameters.position_pct, 0, 100);
if (new Set(["front-left", "front-right", "rear-left", "rear-right"]).has(target)
&& position !== null) {
return command("单窗位置", `window ${target} position ${position}`);
}
}
if (capability === "climate.set_power" && onOff) {
return command("空调电源", `ac ${onOff}`);
}
if (capability === "climate.set_fan_level") {
const level = boundedInteger(parameters.level ?? parameters.fan_level, 1, 7);
if (level !== null) return command("空调风量", `ac fan ${level}`);
}
if (capability === "climate.set_outlet_mode") {
const mode = hyphenated(parameters.mode ?? parameters.outlet_mode);
const modes = new Set(["face", "face-feet", "feet", "feet-defrost", "defrost", "face-feet-defrost", "face-defrost"]);
if (modes.has(mode)) return command("出风模式", `ac mode ${mode}`);
}
if (capability === "climate.set_temperature") {
const temperature = boundedInteger(parameters.temperature_c, 18, 32);
if (new Set(["driver", "passenger", "rear"]).has(target) && temperature !== null) {
return command("空调温度", `ac temp ${target} ${temperature}`);
}
}
if (capability === "climate.set_control_mode") {
const mode = String(parameters.mode ?? parameters.control_mode ?? "");
if (new Set(["manual", "auto"]).has(mode)) return command("空调控制方式", `ac control ${mode}`);
}
if (capability === "climate.set_cycle_mode") {
const mode = String(parameters.mode ?? parameters.cycle_mode ?? "");
if (new Set(["internal", "external"]).has(mode)) return command("空调循环", `ac cycle ${mode}`);
}
if (capability === "outlet.set_position_percent") {
const horizontal = boundedInteger(parameters.horizontal_pct, 0, 100);
const vertical = boundedInteger(parameters.vertical_pct, 0, 100);
if (new Set(["driver-left", "driver-right", "passenger-left", "passenger-right"]).has(target)
&& horizontal !== null && vertical !== null) {
return command("风口百分比", `outlet ${target} percent ${horizontal} ${vertical}`);
}
}
if (capability === "outlet.set_calibrated_position") {
const horizontal = boundedInteger(parameters.horizontal_value, -128, 126);
const vertical = boundedInteger(parameters.vertical_value, -128, 126);
if (new Set(["driver-left", "driver-right", "passenger-left", "passenger-right"]).has(target)
&& horizontal !== null && vertical !== null) {
return command("风口标定坐标", `outlet ${target} position ${horizontal} ${vertical}`);
}
}
if (new Set(["seat.set_driver_ventilation_native", "seat.set_driver_heating_native"]).has(capability)
&& new Set(["driver", "front-left"]).has(target)) {
const feature = capability.includes("heating") ? "heat" : "vent";
const level = boundedInteger(parameters.level, 1, 2);
const setting = onOff === "off" ? "off" : level === 1 ? "high" : level === 2 ? "low" : null;
if (setting) return command("主驾座椅通风/加热", `seat driver ${feature} ${setting}`);
}
if (capability === "seat.set_driver_massage_native"
&& new Set(["driver", "front-left"]).has(target)) {
const action = String(parameters.action || "");
if (action === "off") return command("主驾座椅按摩", "seat driver massage off");
if (action === "strength") {
const strength = { 1: "weak", 2: "medium", 3: "strong" }[Number(parameters.strength)]
|| (new Set(["weak", "medium", "strong"]).has(parameters.strength) ? parameters.strength : null);
if (strength) return command("主驾座椅按摩", `seat driver massage strength ${strength}`);
}
if (action === "mode" && new Set(["wave", "pulse", "stretch", "slack", "relax"]).has(parameters.mode)) {
return command("主驾座椅按摩", `seat driver massage mode ${parameters.mode}`);
}
}
if (capability === "seat.set_driver_axis_native"
&& new Set(["driver", "front-left"]).has(target)) {
const axis = String(parameters.axis || "");
const direction = String(parameters.direction || "");
const validDirections = {
horizontal: new Set(["forward", "backward", "stop"]),
height: new Set(["up", "down", "stop"]),
backrest: new Set(["forward", "backward", "stop"]),
basin: new Set(["up", "down", "stop"]),
};
if (validDirections[axis]?.has(direction)) {
if (direction === "stop") return command("主驾座椅调节", `seat driver ${axis} stop`);
const duration = boundedInteger(parameters.duration_ms, 100, 2000);
if (duration !== null) return command("主驾座椅调节", `seat driver ${axis} ${direction} ${duration}`);
}
}
if (capability === "seat.set_driver_position_native") {
const position = boundedInteger(parameters.position ?? parameters.slot, 1, 3);
if (position !== null) return command("主驾座椅记忆", `seat driver position ${position}`);
}
if (capability === "seat.recall_driver_memory_native") {
const position = boundedInteger(parameters.position ?? parameters.slot, 1, 3);
if (position !== null) return command("主驾座椅记忆", `seat driver memory recall ${position}`);
}
if (capability === "interior_light.set_indoor_power" && onOff) {
return command("室内灯总开关", `light indoor ${onOff}`);
}
if (capability === "interior_light.set_reading_power" && target === "all" && onOff) {
return command("全部阅读灯", `light reading all ${onOff}`);
}
if (capability === "ambient_light.set_power" && onOff) {
return command("氛围灯", `light ambient ${onOff}`);
}
if (capability === "ambient_light.set_color_index") {
const color = boundedInteger(parameters.color_index, 1, 127);
if (color !== null) return command("氛围灯", `light ambient color ${color}`);
}
if (capability === "ambient_light.set_brightness_level") {
const level = boundedInteger(parameters.brightness_level, 1, 5);
if (level !== null) return command("氛围灯", `light ambient brightness ${level}`);
}
if (capability === "roof.set_shade_motion") {
const motion = String(parameters.motion || "");
if (new Set(["open", "close", "half", "stop"]).has(motion)) {
return command("遮阳帘", `shade ${motion}`);
}
}
if (capability === "roof.set_shade_position") {
const position = boundedInteger(parameters.position_pct ?? parameters.shade_position_pct, 0, 100);
if (position !== null) return command("遮阳帘", `shade position ${position}`);
}
return null;
}
function parsedCanCommands(result = {}) {
const configurations = result.function_configs || {};
const plans = result.command_plans || {};
const planKeys = new Set(Object.keys(plans));
const hasCompiledPlan = planKeys.size > 0 || Boolean(result.command_plan);
if (!hasCompiledPlan) return [];
const commands = [];
Object.entries(configurations).forEach(([configKey, configuration]) => {
if (planKeys.size > 0 && !planKeys.has(configKey)) return;
(configuration?.spec?.operations || []).forEach((operation) => {
const item = parsedCanCommandForOperation(operation);
if (item) commands.push(item);
});
});
return [...new Map(commands.map((item) => [`${item.channel}:${item.command}`, item])).values()];
}
function renderParsedCanCommands(result, { message = "" } = {}) {
const panel = $("#v2CanCommandPanel");
const list = $("#v2CanCommandList");
const count = $("#v2CanCommandCount");
if (!panel || !list || !count) return;
const commands = result ? parsedCanCommands(result) : [];
count.textContent = String(commands.length);
panel.dataset.state = commands.length ? "ready" : message ? "pending" : "empty";
if (commands.length) {
list.innerHTML = `<ol>${commands.map((item) => `
<li data-can-channel="${escapeHtml(item.channel)}">
<span><b class="v2-can-command-channel">${escapeHtml(item.channel)}</b>${escapeHtml(item.category)}</span>
<code>${escapeHtml(item.command)}</code>
${item.frame ? `<small class="v2-can-command-frame">seat-ota · ${escapeHtml(item.frame)}</small>` : ""}
</li>`).join("")}</ol>`;
} else {
list.innerHTML = `<p>${escapeHtml(message || "本次请求未生成已验证的 CAN 控制指令")}</p>`;
}
requestAnimationFrame(syncSidebarLogBaseline);
}
function closeReasoningPicker() {
@@ -105,7 +355,6 @@
trigger.setAttribute("aria-expanded", "false");
};
const open = () => {
closeCustomDropdowns();
reasoningField.hidden = false;
trigger.classList.add("open");
trigger.setAttribute("aria-expanded", "true");
@@ -142,156 +391,25 @@
sync();
}
function enhanceSelect(select) {
if (!select || dropdownControls.has(select)) return;
const shell = select.parentElement;
if (!shell) return;
shell.classList.add("v2-custom-select");
select.classList.add("v2-native-select");
const trigger = make("button", "v2-dropdown-trigger", `
<span class="v2-dropdown-value"></span>
<span class="v2-dropdown-caret" aria-hidden="true"></span>`);
trigger.type = "button";
trigger.setAttribute("aria-haspopup", "listbox");
trigger.setAttribute("aria-expanded", "false");
trigger.setAttribute("aria-label", select.getAttribute("aria-label") || "打开下拉菜单");
const menu = make("div", "v2-dropdown-menu", "");
menu.id = `${select.id}Menu`;
menu.hidden = true;
menu.setAttribute("role", "listbox");
menu.setAttribute("aria-label", select.getAttribute("aria-label") || "选项");
trigger.setAttribute("aria-controls", menu.id);
shell.append(trigger, menu);
const valueNode = $(".v2-dropdown-value", trigger);
const render = () => {
const selected = select.selectedOptions[0] || select.options[0];
valueNode.textContent = selected?.textContent?.trim() || "请选择";
trigger.disabled = select.disabled;
menu.innerHTML = "";
[...select.options].forEach((option) => {
const item = make("button", "v2-dropdown-option", "");
item.type = "button";
item.dataset.value = option.value;
item.textContent = option.textContent.trim();
item.disabled = option.disabled;
item.setAttribute("role", "option");
item.setAttribute("aria-selected", String(option.value === select.value));
menu.append(item);
});
};
const close = () => {
menu.hidden = true;
shell.classList.remove("v2-dropdown-open");
trigger.setAttribute("aria-expanded", "false");
};
const open = () => {
if (trigger.disabled) return;
closeCustomDropdowns();
render();
menu.hidden = false;
shell.classList.add("v2-dropdown-open");
trigger.setAttribute("aria-expanded", "true");
};
const control = { close, menu, open, select, shell, sync: render, trigger };
dropdownControls.set(select, control);
trigger.addEventListener("click", (event) => {
event.stopPropagation();
if (menu.hidden) open();
else close();
});
trigger.addEventListener("keydown", (event) => {
if (event.key === "ArrowDown") {
event.preventDefault();
open();
requestAnimationFrame(() => {
$(".v2-dropdown-option[aria-selected=\"true\"]:not(:disabled)", menu)?.focus();
});
} else if (event.key === "Escape") {
close();
}
});
menu.addEventListener("click", (event) => {
event.stopPropagation();
const item = event.target.closest(".v2-dropdown-option");
if (!item || item.disabled) return;
select.value = item.dataset.value;
select.dispatchEvent(new Event("change", { bubbles: true }));
render();
close();
trigger.focus();
});
menu.addEventListener("keydown", (event) => {
if (event.key !== "Escape") return;
event.preventDefault();
close();
trigger.focus();
});
select.addEventListener("change", render);
new MutationObserver(render).observe(select, {
attributes: true,
characterData: true,
childList: true,
subtree: true,
});
render();
}
function enhanceConfiguredDropdowns() {
[$("#vehicleSelect")]
.filter(Boolean)
.forEach(enhanceSelect);
}
function buildSidebar(commandPanel) {
commandPanel.classList.add("v2-sidebar");
commandPanel.innerHTML = "";
const profile = make("section", "v2-profile", `
<div class="v2-avatar" aria-hidden="true"><span></span></div>
<div class="v2-profile-copy">
<small>WELCOME TO THE CABIN</small>
<div class="v2-role-select">
<span>Hi,</span><strong>user</strong>
</div>
</div>
<button class="v2-sidebar-toggle" id="v2SidebarToggle" type="button" aria-label="折叠左侧控制台" aria-expanded="true">
<i data-lucide="panel-left-close"></i>
</button>`);
const control = make("section", "v2-sidebar-section v2-control-section", `
<div class="v2-section-heading">
<span>CONTROL DESK</span>
<h2>控制台</h2>
</div>
<span class="v2-field-caption">控制方式</span>
<div class="v2-control-switch" role="group" aria-label="控制方式">
<button type="button" data-v2-control="natural"><i data-lucide="message-square-text"></i><span>自然语言控制</span></button>
<button type="button" data-v2-control="gesture"><i data-lucide="scan-face"></i><span>手势控制</span></button>
</div>`);
const config = make("section", "v2-sidebar-section v2-config-section", `
<div class="v2-divider"></div>
<span class="v2-field-caption">配置信息</span>
<div class="v2-config-stack" id="v2ConfigStack"></div>`);
const sidebarToggle = make("button", "v2-sidebar-toggle", '<i data-lucide="panel-left-close"></i>');
sidebarToggle.id = "v2SidebarToggle";
sidebarToggle.type = "button";
sidebarToggle.setAttribute("aria-label", "折叠左侧控制台");
sidebarToggle.setAttribute("aria-expanded", "true");
const footer = make("footer", "v2-sidebar-footer", `
<span class="v2-live-dot"></span>
<div><strong>智舱控制链路</strong><small id="v2ConnectionText">正在连接服务</small></div>
<button type="button" id="v2LogoutButton" title="退出登录" aria-label="退出登录"><i data-lucide="log-out"></i></button>`);
commandPanel.append(profile, control, config, footer);
return $("#v2ConfigStack");
commandPanel.append(sidebarToggle, footer);
}
function buildConfigFields(configStack, originalForm) {
function prepareRuntimeControls(originalForm) {
const vehicleField = $(".vehicle-field", originalForm);
const transportField = $("#vehicleTransportField", originalForm);
const providerField = $(".field-group:has(.segmented [data-provider])", originalForm);
@@ -299,72 +417,17 @@
const executionField = $(".field-group:has(.execution-mode-options)", originalForm);
const simulationFaultField = $("#simulationFaultField", originalForm);
if (vehicleField) {
vehicleField.classList.add("v2-config-field", "v2-vehicle-field");
const vehicleControl = $(".vehicle-select-control", vehicleField);
const vehicleSelect = $("#vehicleSelect", vehicleField);
const vehicleIcon = $("svg, [data-lucide]", vehicleControl);
if (vehicleControl && vehicleSelect && vehicleIcon) {
const copy = make("div", "v2-config-card-copy", "<small>车型配置</small>");
const selectSlot = make("div", "v2-config-card-select", "");
const state = make("span", "v2-field-state", "CHECK");
state.id = "v2VehicleState";
selectSlot.append(vehicleSelect);
copy.append(selectSlot);
vehicleControl.classList.add("v2-config-card", "v2-config-select-card");
vehicleControl.id = "v2VehicleCard";
vehicleControl.dataset.state = "checking";
vehicleControl.replaceChildren(vehicleIcon, copy, state);
$(".field-label", vehicleField)?.remove();
}
configStack.append(vehicleField);
}
transportField?.classList.add("v2-transport-field");
executionField?.classList.add("v2-execution-field");
simulationFaultField?.classList.add("v2-simulation-fault-field");
if (transportField) {
transportField.classList.add("v2-config-field", "v2-transport-field");
configStack.append(transportField);
}
const modelField = make("div", "v2-config-field v2-config-card v2-model-field", `
<i data-lucide="sparkles"></i>
<div><small>推理模型</small><strong id="v2ModelValue">模型检测中</strong></div>
<span class="v2-field-state" id="v2ModelState">AUTO</span>
<p class="provider-note" id="v2ModelNote">模型由服务端安全配置</p>`);
modelField.id = "v2ModelField";
configStack.append(modelField);
installReasoningPicker(reasoningField, originalForm);
const gestureModelField = make("div", "v2-config-field v2-config-card v2-gesture-model-field", `
<i data-lucide="scan-line"></i>
<div><small>手势模型</small><strong id="v2GestureModelValue">MediaPipe GR</strong></div>
<span class="v2-field-state">VISION</span>
<small class="v2-gesture-model-note" id="v2GestureModelNote">Tasks Vision · 0.10.14</small>`);
gestureModelField.id = "v2GestureModelField";
configStack.append(gestureModelField);
const userHardwareState = make("div", "v2-user-target-status", `
<i></i><span id="v2UserTargetText">正在准备虚拟 CAN</span>`);
configStack.append(userHardwareState);
if (providerField) {
providerField.classList.add("v2-provider-field");
providerField.hidden = true;
configStack.append(providerField);
}
if (executionField) {
executionField.classList.add("v2-native-execution-field");
executionField.hidden = true;
configStack.append(executionField);
}
const advanced = make("div", "v2-advanced-stack", "");
[simulationFaultField].forEach((field) => {
[vehicleField, providerField].forEach((field) => {
if (!field) return;
field.classList.add("v2-config-field", "v2-advanced-field");
advanced.append(field);
field.classList.add("v2-runtime-data-field");
field.hidden = true;
});
configStack.append(advanced);
}
function buildOperation(mainPanel, originalForm, panelHeading, modeRail, requestHeader, gestureDrawer) {
@@ -582,16 +645,14 @@
const syncedDevices = [
{
id: "passenger", name: "副驾座椅", icon: "armchair",
rows: () => [["通风", seatFunctionValue("passenger", "ventilation")], ["加热", seatFunctionValue("passenger", "heating")], ["水平", seatAxisValue("passenger", "longitudinal")], ["坐垫", seatAxisValue("passenger", "cushion_tilt")], ["靠背", seatAxisValue("passenger", "backrest_recline")], ["腿托", seatAxisValue("passenger", "legrest_height")]],
rows: () => [["前后", seatAxisValue("passenger", "longitudinal")], ["高度", seatAxisValue("passenger", "cushion_tilt")], ["靠背", seatAxisValue("passenger", "backrest_recline")], ["腿托", seatAxisValue("passenger", "legrest_height")]],
controls: () => [
selectControl("ventilation", "通风", seatLevelControlValue("passenger", "ventilation"), levelOptions),
selectControl("heating", "加热", seatLevelControlValue("passenger", "heating"), levelOptions),
rangeControl("longitudinal", "水平", seatAxisValue("passenger", "longitudinal")),
rangeControl("cushion_tilt", "坐垫", seatAxisValue("passenger", "cushion_tilt")),
rangeControl("longitudinal", "前后", seatAxisValue("passenger", "longitudinal")),
rangeControl("cushion_tilt", "高度", seatAxisValue("passenger", "cushion_tilt")),
rangeControl("backrest_recline", "靠背", seatAxisValue("passenger", "backrest_recline")),
rangeControl("legrest_height", "腿托", seatAxisValue("passenger", "legrest_height")),
],
command: (values) => `调节副驾座椅:通风${optionLabel(levelOptions, values.ventilation)},加热${optionLabel(levelOptions, values.heating)},水平${values.longitudinal}%,坐垫${values.cushion_tilt}%,靠背${values.backrest_recline}%,腿托${values.legrest_height}%`,
command: (values) => `调节副驾座椅:前后${values.longitudinal}%,座椅高度${values.cushion_tilt}%,靠背${values.backrest_recline}%,腿托${values.legrest_height}%`,
},
{
id: "driver", name: "主驾座椅", icon: "armchair",
@@ -1006,6 +1067,11 @@
button.classList.toggle("v2-sync-active", active);
});
renderTaskTimeline(task);
if (normalizedStatus === "running") {
renderParsedCanCommands(null, { message: "正在解析自然语言并生成 CAN 控制指令…" });
} else if (result) {
renderParsedCanCommands(result);
}
}
function applyDisplaySnapshot(snapshot) {
@@ -1027,9 +1093,6 @@
: 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) {
@@ -1124,7 +1187,26 @@
const sidebar = $(".v2-sidebar");
const lowerPanel = $("#v2DevicePanel") || $(".joint-twin-shell > .twin-band");
if (!sidebar || !lowerPanel) return;
const offset = Math.max(0, lowerPanel.getBoundingClientRect().top - sidebar.getBoundingClientRect().top);
const sidebarRect = sidebar.getBoundingClientRect();
const overview = $(".v2-sync-sidebar-overview", sidebar);
const overviewRect = overview?.getBoundingClientRect();
const overviewBottom = overview && overviewRect
? Array.from(overview.children).reduce(
(bottom, child) => Math.max(bottom, child.getBoundingClientRect().bottom),
overviewRect.top,
)
: 0;
const overviewPaddingBottom = overview
? Number.parseFloat(getComputedStyle(overview).paddingBottom) || 0
: 0;
const requiredOverviewHeight = overviewRect
? Math.ceil(overviewBottom - overviewRect.top + overviewPaddingBottom + 10)
: 0;
const offset = Math.max(
0,
lowerPanel.getBoundingClientRect().top - sidebarRect.top,
requiredOverviewHeight,
);
sidebar.style.setProperty("--v2-sync-log-top", `${offset}px`);
}
@@ -1143,7 +1225,6 @@
<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>
<div class="v2-sync-protocol"><i data-lucide="radio-tower"></i><div><small>扩展链路</small><strong id="v2SyncTransport">GSCU · 等待连接</strong></div></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>
@@ -1151,7 +1232,39 @@
</section>`);
if (sidebar && sidebarFooter) {
sidebar.insertBefore(syncSidebar, sidebarFooter);
$(".v2-sync-sidebar-overview", syncSidebar)?.append(sidebarFooter);
const syncOverview = $(".v2-sync-sidebar-overview", syncSidebar);
const transportField = $("#vehicleTransportField");
const executionField = $(".field-group:has(.execution-mode-options)");
const simulationFaultField = $("#simulationFaultField");
if (syncOverview) {
const linkStack = $(".v2-sync-link-stack", syncOverview);
const insertVisibleControl = (field, className) => {
if (!field) return;
field.classList.add(className);
if (linkStack) syncOverview.insertBefore(field, linkStack);
else syncOverview.append(field);
};
insertVisibleControl(transportField, "v2-sync-transport-field");
if (executionField) executionField.hidden = false;
insertVisibleControl(executionField, "v2-sync-execution-field");
insertVisibleControl(simulationFaultField, "v2-sync-simulation-fault-field");
const canCommandPanel = make("details", "v2-can-command-panel", `
<summary>
<span><i data-lucide="binary"></i><b>已解析 CAN 控制指令</b></span>
<strong id="v2CanCommandCount">0</strong>
<i data-lucide="chevron-down"></i>
</summary>
<div class="v2-can-command-list" id="v2CanCommandList">
<p>等待自然语言解析结果</p>
</div>`);
canCommandPanel.id = "v2CanCommandPanel";
if (linkStack) syncOverview.insertBefore(canCommandPanel, linkStack);
else syncOverview.append(canCommandPanel);
canCommandPanel.addEventListener("toggle", () => {
requestAnimationFrame(syncSidebarLogBaseline);
});
}
syncOverview?.append(sidebarFooter);
const sidebarToggle = $("#v2SidebarToggle", sidebar);
if (sidebarToggle) $(".v2-sync-sidebar-heading", syncSidebar)?.append(sidebarToggle);
}
@@ -1234,8 +1347,12 @@
if (event.target.matches("select[data-device-control]")) storeDeviceControlDraft(event.target);
});
$("#v2LogFilter")?.addEventListener("change", renderSyncLogs);
$("#requestForm")?.addEventListener("submit", () => {
renderParsedCanCommands(null, { message: "正在解析自然语言并生成 CAN 控制指令…" });
});
window.addEventListener("cognitive-agent:result", (event) => {
const result = event.detail || {};
renderParsedCanCommands(result);
appendSyncLog({
key: result.request_id ? `result:${result.request_id}` : "",
category: "task",
@@ -1259,6 +1376,7 @@
renderSyncedDevices();
}
if (event.detail?.error) {
renderParsedCanCommands(null, { message: "本次请求未生成 CAN 控制指令" });
appendSyncLog({ category: "task", title: "编排任务执行失败", detail: event.detail.error.message || "请求失败", status: "error" });
}
});
@@ -1376,23 +1494,18 @@
if (requestStatus && statusTarget && requestStatus.parentElement !== statusTarget) {
statusTarget.append(requestStatus);
}
$$('[data-v2-control]').forEach((button) => {
const active = button.dataset.v2Control === ui.controlMode;
button.classList.toggle("active", active);
button.setAttribute("aria-pressed", String(active));
});
requestAnimationFrame(updateModeEdgeHints);
const drawer = $("#gestureDrawer");
if (!triggerGesture) {
const gestureVisible = ui.controlMode === "gesture";
drawer?.setAttribute("aria-hidden", String(!gestureVisible));
$("#gestureToggleButton")?.setAttribute("aria-expanded", String(gestureVisible));
drawer?.classList.toggle("open", gestureVisible);
return;
}
if (ui.controlMode === "gesture") {
if (!drawer?.classList.contains("open")) $("#gestureToggleButton")?.click();
window.CognitiveAgentGesture?.open?.();
} else if (drawer?.classList.contains("open")) {
$("#gestureCloseButton")?.click();
window.CognitiveAgentGesture?.close?.();
}
}
@@ -1441,120 +1554,53 @@
window.setTimeout(() => window.dispatchEvent(new Event("resize")), 180);
}
function requestUserSafeExecutionMode() {
const dryRunButton = $('[data-execution-mode="dry_run"]');
const note = $("#v2ExecutionNote");
const userTarget = $(".v2-user-target-status");
const userTargetText = $("#v2UserTargetText");
function keepRunButtonAvailable() {
const runButton = $("#runButton");
if (!dryRunButton) return;
if (!dryRunButton.classList.contains("active")) dryRunButton.click();
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;
syncExecutionSelector();
}
function syncExecutionSelector() {
const select = $("#v2ExecutionSelect");
if (!select) return;
const active = $('[data-execution-mode].active');
if (active) select.value = active.dataset.executionMode;
["dry_run", "simulated_hardware", "hardware"].forEach((value) => {
const option = $(`option[value="${value}"]`, select);
const button = $(`[data-execution-mode="${value}"]`);
if (option && button) option.disabled = button.disabled;
});
dropdownControls.get(select)?.sync();
}
function syncRuntimePresentation() {
const modelBadge = $("#modelBadge");
const connectionBadge = $("#connectionBadge");
const modelValue = $("#v2ModelValue");
const modelNote = $("#v2ModelNote");
function syncRuntimePresentation(connectionState = window.CognitiveAgentNetwork?.getState?.()) {
const connectionText = $("#v2ConnectionText");
if (modelBadge && modelValue) {
const text = modelBadge.textContent.trim() || "模型检测中";
modelValue.textContent = text;
const unavailable = modelBadge.classList.contains("danger");
$("#v2ModelField")?.classList.toggle("unavailable", unavailable);
$("#v2ModelState").textContent = unavailable ? "LOCAL" : "READY";
if (modelNote) modelNote.textContent = unavailable
? "GPT 不可用,自动使用本地规则"
: "服务端安全托管 · 失败自动降级";
}
if (connectionBadge && connectionText) {
connectionText.textContent = connectionBadge.textContent.trim() || "正在连接服务";
const online = !connectionBadge.classList.contains("danger")
&& !connectionBadge.classList.contains("warning");
$(".v2-sidebar-footer")?.classList.toggle("offline", !online);
}
syncExecutionSelector();
requestUserSafeExecutionMode();
}
function setVehicleLinkState(connected) {
const card = $("#v2VehicleCard");
const state = $("#v2VehicleState");
if (!card || !state) return;
card.dataset.state = connected ? "online" : "offline";
state.textContent = connected ? "READY" : "OFFLINE";
}
async function refreshVehicleLinkState() {
const card = $("#v2VehicleCard");
const state = $("#v2VehicleState");
if (card) card.dataset.state = "checking";
if (state) state.textContent = "CHECK";
try {
const response = await fetch("/api/extension/health", {
credentials: "same-origin",
headers: { Accept: "application/json" },
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const health = await response.json();
setVehicleLinkState(Boolean(health.vehicle?.connected));
} catch (_error) {
setVehicleLinkState(false);
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",
);
}
keepRunButtonAvailable();
}
function bindEvents() {
$$('[data-v2-control]').forEach((button) => {
button.addEventListener("click", () => {
setControlMode(button.dataset.v2Control);
});
});
$$('.v2-module-tab').forEach((button) => {
button.addEventListener("click", () => {
if (!button.disabled) setModule(button.dataset.module);
});
});
$("#v2ExecutionSelect")?.addEventListener("change", (event) => {
$(`[data-execution-mode="${event.target.value}"]`)?.click();
syncExecutionSelector();
});
$$(".v2-page-button").forEach((button) => {
button.addEventListener("click", () => setPage(button.dataset.v2PageTarget));
});
$("#v2LogoutButton")?.addEventListener("click", () => $("#logoutButton")?.click());
window.addEventListener("cognitive-agent:connection", (event) => {
if (event.detail?.status === "online") refreshVehicleLinkState();
else if (["offline", "degraded"].includes(event.detail?.status)) setVehicleLinkState(false);
});
$("#v2LogoutButton")?.addEventListener(
"click",
() => window.CognitiveAgentConsole?.logout?.(),
);
$("#v2SidebarToggle")?.addEventListener(
"click",
() => setSidebarCollapsed(!ui.sidebarCollapsed),
);
document.addEventListener("click", () => {
closeCustomDropdowns();
closeReasoningPicker();
});
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
closeCustomDropdowns();
closeReasoningPicker();
clearTwinDevicePreview({ force: true });
}
@@ -1583,8 +1629,11 @@
.observe(modeChips, { childList: true });
}
const observer = new MutationObserver(syncRuntimePresentation);
[$("#modelBadge"), $("#connectionBadge"), $("#hardwareMode"), $("#simulationMode")]
window.addEventListener("cognitive-agent:connection", (event) => {
syncRuntimePresentation(event.detail);
});
const observer = new MutationObserver(() => keepRunButtonAvailable());
[$("#hardwareMode"), $("#simulationMode")]
.filter(Boolean)
.forEach((node) => observer.observe(node, {
attributes: true,
@@ -1604,8 +1653,6 @@
document.body.classList.add("console-v2");
const panelHeading = $(".panel-heading", commandPanel);
const modeRail = $(".mode-rail", commandPanel);
const exampleGroup = $(".example-group", commandPanel);
const safetyStrip = $("#safetyStrip", commandPanel);
const requestHeader = $(".request-header", mainPanel);
const gestureDrawer = $("#gestureDrawer");
@@ -1615,18 +1662,11 @@
requestHeader,
gestureDrawer,
originalForm,
exampleGroup,
safetyStrip,
};
Object.values(detached).filter(Boolean).forEach((node) => node.remove());
const legacyParking = make("div", "v2-legacy-parking v2-legacy-hidden", "");
[exampleGroup, safetyStrip].filter(Boolean).forEach((node) => legacyParking.append(node));
document.body.append(legacyParking);
const configStack = buildSidebar(commandPanel);
buildConfigFields(configStack, originalForm);
enhanceConfiguredDropdowns();
buildSidebar(commandPanel);
prepareRuntimeControls(originalForm);
buildOperation(
mainPanel,
originalForm,
@@ -1636,9 +1676,6 @@
gestureDrawer,
);
buildModuleSwitcher(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();
@@ -1647,18 +1684,18 @@
setSidebarCollapsed(ui.sidebarCollapsed);
setPage(0);
syncRuntimePresentation();
refreshVehicleLinkState();
window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } });
requestAnimationFrame(() => {
updateModeEdgeHints();
document.body.classList.add("v2-ready");
window.setTimeout(() => $("#v2BootScreen")?.remove(), 220);
});
}
window.CognitiveAgentConsoleV2 = {
enhanceSelect,
getModule: () => ui.activeModule,
getRole: () => "user",
getParsedCanCommands: (result) => parsedCanCommands(result).map((item) => ({ ...item })),
setModule,
};