优化前端,优化前端指令,优化自定义控制
This commit is contained in:
+988
-205
File diff suppressed because it is too large
Load Diff
+406
-657
File diff suppressed because it is too large
Load Diff
+367
-330
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -56,6 +56,8 @@ const state = {
|
||||
customizeOpen: false,
|
||||
engine: null,
|
||||
runtimeReady: false,
|
||||
enabled: true,
|
||||
disabledReason: "",
|
||||
cameraStatus: "checking",
|
||||
cameraMessage: "正在检测摄像头…",
|
||||
cameraCheckSequence: 0,
|
||||
@@ -114,7 +116,8 @@ function cameraFailureDetails(error) {
|
||||
function syncCameraButtonAvailability() {
|
||||
const button = $("#gestureStartButton");
|
||||
if (!button || state.stream) return;
|
||||
button.disabled = !state.runtimeReady
|
||||
button.disabled = !state.enabled
|
||||
|| !state.runtimeReady
|
||||
|| ["checking", "unavailable", "unsupported"].includes(state.cameraStatus);
|
||||
}
|
||||
|
||||
@@ -693,29 +696,48 @@ function resetMapping() {
|
||||
}
|
||||
|
||||
function openDrawer() {
|
||||
$("#gestureDrawer").classList.add("open");
|
||||
$("#gestureDrawer").setAttribute("aria-hidden", "false");
|
||||
$("#gestureBackdrop").hidden = false;
|
||||
requestAnimationFrame(() => $("#gestureBackdrop").classList.add("visible"));
|
||||
$("#gestureToggleButton").setAttribute("aria-expanded", "true");
|
||||
if (!state.enabled) {
|
||||
notify(state.disabledReason || "当前运行模式未启用手势控制");
|
||||
return false;
|
||||
}
|
||||
const drawer = $("#gestureDrawer");
|
||||
if (!drawer) return false;
|
||||
drawer.classList.add("open");
|
||||
drawer.setAttribute("aria-hidden", "false");
|
||||
document.body.classList.add("gesture-open");
|
||||
return true;
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
stopCamera();
|
||||
$("#gestureDrawer").classList.remove("open");
|
||||
$("#gestureDrawer").setAttribute("aria-hidden", "true");
|
||||
$("#gestureBackdrop").classList.remove("visible");
|
||||
window.setTimeout(() => { $("#gestureBackdrop").hidden = true; }, 240);
|
||||
$("#gestureToggleButton").setAttribute("aria-expanded", "false");
|
||||
const drawer = $("#gestureDrawer");
|
||||
drawer?.classList.remove("open");
|
||||
drawer?.setAttribute("aria-hidden", "true");
|
||||
document.body.classList.remove("gesture-open");
|
||||
}
|
||||
|
||||
function setEnabled(enabled, { reason = "" } = {}) {
|
||||
state.enabled = Boolean(enabled);
|
||||
state.disabledReason = state.enabled ? "" : String(reason || "当前运行模式未启用手势控制");
|
||||
if (!state.enabled) closeDrawer();
|
||||
syncCameraButtonAvailability();
|
||||
const drawer = $("#gestureDrawer");
|
||||
if (drawer) {
|
||||
drawer.dataset.enabled = String(state.enabled);
|
||||
drawer.title = state.disabledReason;
|
||||
}
|
||||
}
|
||||
|
||||
window.CognitiveAgentGesture = {
|
||||
open: openDrawer,
|
||||
close: closeDrawer,
|
||||
isOpen: () => $("#gestureDrawer")?.classList.contains("open") === true,
|
||||
setEnabled,
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
$("#gestureStartButton").disabled = true;
|
||||
$("#gestureToggleButton")?.addEventListener("click", openDrawer);
|
||||
$("#gestureCloseButton")?.addEventListener("click", closeDrawer);
|
||||
$("#gestureBackdrop")?.addEventListener("click", closeDrawer);
|
||||
$("#gestureStartButton")?.addEventListener("click", toggleCamera);
|
||||
$("#gestureStopButton")?.addEventListener("click", () => state.engine?.setArmed(false));
|
||||
$("#gestureCustomizeButton")?.addEventListener("click", toggleCustomize);
|
||||
|
||||
+88
-122
@@ -9,44 +9,26 @@
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="icon" href="/icons/app-icon-192.png" />
|
||||
<title>511工程-智能座舱</title>
|
||||
<link rel="stylesheet" href="/styles.css?v=dual-seat-10" />
|
||||
<link rel="stylesheet" href="/console-v2.css?v=sync-layout-27" />
|
||||
<link rel="stylesheet" href="/styles.css?v=dual-seat-16" />
|
||||
<link rel="stylesheet" href="/console-v2.css?v=sync-layout-40" />
|
||||
<link rel="stylesheet" href="/self-extension.css?v=sync-layout-26" />
|
||||
<script src="/vendor/lucide/0.468.0/lucide.min.js" defer></script>
|
||||
<script src="/vendor/three/0.147.0/three.min.js" defer></script>
|
||||
<script src="/vendor/three/0.147.0/fflate.min.js" defer></script>
|
||||
<script src="/vendor/three/0.147.0/FBXLoader.js" defer></script>
|
||||
<script src="/network.js?v=android-pad-1" defer></script>
|
||||
<script src="/app.js?v=dual-seat-90" defer></script>
|
||||
<script type="module" src="/gesture-control.js?v=android-pad-8"></script>
|
||||
<script src="/pwa.js?v=android-pad-2" defer></script>
|
||||
<script src="/console-v2.js?v=sync-layout-27" defer></script>
|
||||
<script src="/network.js?v=android-pad-2" defer></script>
|
||||
<script src="/app.js?v=dual-seat-101" defer></script>
|
||||
<script type="module" src="/gesture-control.js?v=android-pad-9"></script>
|
||||
<script src="/pwa.js?v=android-pad-3" defer></script>
|
||||
<script src="/console-v2.js?v=sync-layout-41" defer></script>
|
||||
<script src="/self-extension.js?v=sync-layout-26" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<div class="brand-block">
|
||||
<span class="brand-mark" aria-hidden="true"><i data-lucide="brain-circuit"></i></span>
|
||||
<div>
|
||||
<h1>Cognitive Agent</h1>
|
||||
<p>CABIN ORCHESTRATION <span>/</span> MULTI-DOMAIN</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="runtime-badges" aria-label="运行环境">
|
||||
<button class="gesture-entry" id="gestureToggleButton" type="button" aria-controls="gestureDrawer" aria-expanded="false">
|
||||
<span class="gesture-entry-dot"></span><i data-lucide="scan-face"></i>手势控制
|
||||
</button>
|
||||
<span class="status-badge neutral connection-badge" id="connectionBadge"><i data-lucide="loader-circle"></i><span>连接中</span></span>
|
||||
<button class="status-badge neutral pwa-badge" id="pwaBadge" type="button" hidden></button>
|
||||
<span class="status-badge neutral" id="executionBadge"><i data-lucide="flask-conical"></i>DRY-RUN</span>
|
||||
<span class="status-badge" id="modelBadge"><i data-lucide="sparkles"></i>模型检测中</span>
|
||||
<span class="status-badge danger" id="hardwareBadge"><i data-lucide="shield-off"></i>硬件检测中</span>
|
||||
<button class="status-badge auth-logout" id="logoutButton" type="button" title="退出登录" aria-label="退出登录" hidden>
|
||||
<i data-lucide="log-out"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<body class="console-v2">
|
||||
<div class="v2-boot-screen" id="v2BootScreen" role="status" aria-live="polite">
|
||||
<span class="v2-boot-mark" aria-hidden="true"><i></i><i></i><i></i></span>
|
||||
<strong>511工程-智能座舱</strong>
|
||||
<small>正在加载车机控制与实时状态</small>
|
||||
</div>
|
||||
<div class="workspace">
|
||||
<aside class="command-panel">
|
||||
<div class="panel-heading">
|
||||
@@ -126,10 +108,10 @@
|
||||
<div class="field-group">
|
||||
<span class="field-label">执行目标</span>
|
||||
<div class="segmented execution-mode-options" role="group" aria-label="执行目标">
|
||||
<button type="button" class="mode-option active" data-execution-mode="dry_run">
|
||||
<button type="button" class="mode-option" data-execution-mode="dry_run">
|
||||
<i data-lucide="flask-conical"></i>虚拟 CAN
|
||||
</button>
|
||||
<button type="button" class="mode-option" id="hardwareMode" data-execution-mode="hardware" disabled>
|
||||
<button type="button" class="mode-option active" id="hardwareMode" data-execution-mode="hardware" disabled>
|
||||
<i data-lucide="radio-tower"></i>真实台架
|
||||
</button>
|
||||
<button type="button" class="mode-option" id="simulationMode" data-execution-mode="simulated_hardware" disabled>
|
||||
@@ -144,10 +126,10 @@
|
||||
<span class="field-label">车辆连接方式</span>
|
||||
<div class="segmented vehicle-transport-options" role="group" aria-label="车辆连接方式">
|
||||
<button type="button" class="mode-option active" data-control-transport="wired_can">
|
||||
<i data-lucide="cable"></i>有线控制
|
||||
<i data-lucide="cable"></i>有线 CAN
|
||||
</button>
|
||||
<button type="button" class="mode-option" data-control-transport="wireless_mqtt">
|
||||
<i data-lucide="wifi"></i>无线控制
|
||||
<i data-lucide="wifi"></i>无线 CAN
|
||||
</button>
|
||||
</div>
|
||||
<p class="provider-note" id="vehicleTransportNote">正在检查车辆连接</p>
|
||||
@@ -183,47 +165,6 @@
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="example-group">
|
||||
<span class="field-label">示例请求</span>
|
||||
<button type="button" class="example-command featured-example" data-example="副驾座椅通风3档,同时打开空调,温度设为24度,风量3档">
|
||||
<i data-lucide="workflow"></i><span>副驾通风 + 空调 24℃ · 双通道联动</span><i data-lucide="chevron-right"></i>
|
||||
</button>
|
||||
<button type="button" class="example-command" data-example="空调打开,车内温度调到24度,风量3档">
|
||||
<i data-lucide="fan"></i><span>空调 24℃ · 风量 3 档</span><i data-lucide="chevron-right"></i>
|
||||
</button>
|
||||
<button type="button" class="example-command" data-example="天幕透光率调到30%,遮阳帘打开一半">
|
||||
<i data-lucide="panels-top-left"></i><span>天幕透光 30% · 遮阳帘 50%</span><i data-lucide="chevron-right"></i>
|
||||
</button>
|
||||
<button type="button" class="example-command" data-example="打开氛围灯,调成冰蓝色,亮度60%">
|
||||
<i data-lucide="lamp-ceiling"></i><span>冰蓝氛围灯 · 亮度 60%</span><i data-lucide="chevron-right"></i>
|
||||
</button>
|
||||
<button type="button" class="example-command featured-example" data-example="进入观影状态:副驾调整为舒适观影姿态,空调24度、风量2档,天幕调暗,氛围灯换成暖橙色">
|
||||
<i data-lucide="clapperboard"></i><span>跨域观影场景</span><i data-lucide="chevron-right"></i>
|
||||
</button>
|
||||
<button type="button" class="example-command" data-example="副驾有点热,有点闷">
|
||||
<i data-lucide="wind"></i><span>副驾有点热,有点闷</span><i data-lucide="chevron-right"></i>
|
||||
</button>
|
||||
<button type="button" class="example-command" data-example="副驾靠背向后移动5度">
|
||||
<i data-lucide="move-horizontal"></i><span>副驾靠背向后 5°</span><i data-lucide="chevron-right"></i>
|
||||
</button>
|
||||
<button type="button" class="example-command" data-example="副驾调整为零重力模式,要求靠背座椅夹角128度,坐垫脚托夹角133度">
|
||||
<i data-lucide="compass"></i><span>副驾零重力精确夹角</span><i data-lucide="chevron-right"></i>
|
||||
</button>
|
||||
<button type="button" class="example-command" data-example="将当前状态保存为零重力模式">
|
||||
<i data-lucide="bookmark-plus"></i><span>保存当前座椅姿态</span><i data-lucide="chevron-right"></i>
|
||||
</button>
|
||||
<button type="button" class="example-command warning-example" data-example="副驾座椅加热开到三档">
|
||||
<i data-lucide="triangle-alert"></i><span>三档请求(档位虚拟)</span><i data-lucide="chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="safety-strip" id="safetyStrip">
|
||||
<i data-lucide="shield-check"></i>
|
||||
<div>
|
||||
<strong id="safetyTitle">虚拟执行边界</strong>
|
||||
<span id="safetyText">四域 CAN 均为 dry-run,实车发送被强制关闭。</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="main-panel">
|
||||
@@ -242,20 +183,6 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="pipeline" aria-label="执行链路">
|
||||
<div class="pipeline-step" data-stage="intent_parse"><span>01</span><i data-lucide="message-square-text"></i><b>意图抽取</b></div>
|
||||
<i class="pipeline-arrow" data-lucide="chevron-right"></i>
|
||||
<div class="pipeline-step" data-stage="function_generate"><span>02</span><i data-lucide="file-code-2"></i><b>配置生成</b></div>
|
||||
<i class="pipeline-arrow" data-lucide="chevron-right"></i>
|
||||
<div class="pipeline-step" data-stage="domain_validate"><span>03</span><i data-lucide="badge-check"></i><b>双层校验</b></div>
|
||||
<i class="pipeline-arrow" data-lucide="chevron-right"></i>
|
||||
<div class="pipeline-step" data-stage="compile"><span>04</span><i data-lucide="binary"></i><b>CAN 编译</b></div>
|
||||
<i class="pipeline-arrow" data-lucide="chevron-right"></i>
|
||||
<div class="pipeline-step" data-stage="safety_check"><span>05</span><i data-lucide="shield-check"></i><b>安全门禁</b></div>
|
||||
<i class="pipeline-arrow" data-lucide="chevron-right"></i>
|
||||
<div class="pipeline-step" data-stage="execute"><span>06</span><i data-lucide="radio-tower"></i><b id="executeStageLabel">虚拟执行</b></div>
|
||||
</section>
|
||||
|
||||
<section class="cabin-band" aria-labelledby="cabinStateTitle">
|
||||
<div class="section-title-row cabin-heading">
|
||||
<div class="cabin-title-copy">
|
||||
@@ -274,27 +201,27 @@
|
||||
<header>
|
||||
<span class="domain-icon"><i data-lucide="fan"></i></span>
|
||||
<div><strong>空调</strong><small>CMD 0x1DE · FB 0x404 · CH1</small></div>
|
||||
<span class="device-state" id="climatePower">OFF</span>
|
||||
<span class="device-state" id="climatePower">UNKNOWN</span>
|
||||
</header>
|
||||
<p class="domain-capability-line"><span>可控参数</span>电源 · 温度 · 风量/风向</p>
|
||||
<div class="climate-display">
|
||||
<div class="zone-temperature">
|
||||
<span>主驾</span><strong id="driverTemperature">24<sup>℃</sup></strong>
|
||||
<span>主驾</span><strong id="driverTemperature">--<sup>℃</sup></strong>
|
||||
<small>风向<b id="driverAirDirection">-</b></small>
|
||||
</div>
|
||||
<div class="airflow-visual" id="airflowVisual" data-level="0" aria-label="空调风量">
|
||||
<i></i><i></i><i></i><i></i><i></i><i></i><i></i>
|
||||
</div>
|
||||
<div class="zone-temperature passenger-temperature">
|
||||
<span>副驾</span><strong id="passengerTemperature">24<sup>℃</sup></strong>
|
||||
<span>副驾</span><strong id="passengerTemperature">--<sup>℃</sup></strong>
|
||||
<small>风向<b id="passengerAirDirection">-</b></small>
|
||||
</div>
|
||||
</div>
|
||||
<dl class="domain-readouts">
|
||||
<div><dt>风量</dt><dd id="fanLevel">0 档</dd></div>
|
||||
<div><dt>循环</dt><dd id="cycleMode">外循环</dd></div>
|
||||
<div><dt>A/C</dt><dd id="acState">关闭</dd></div>
|
||||
<div><dt>前除霜</dt><dd id="defrostState">关闭</dd></div>
|
||||
<div><dt>风量</dt><dd id="fanLevel">未知</dd></div>
|
||||
<div><dt>循环</dt><dd id="cycleMode">未知</dd></div>
|
||||
<div><dt>A/C</dt><dd id="acState">未知</dd></div>
|
||||
<div><dt>前除霜</dt><dd id="defrostState">未知</dd></div>
|
||||
</dl>
|
||||
</article>
|
||||
|
||||
@@ -321,17 +248,17 @@
|
||||
<header>
|
||||
<span class="domain-icon"><i data-lucide="panels-top-left"></i></span>
|
||||
<div><strong>遮阳帘</strong><small>CMD 0x431 · FB 0x41A · CH1</small></div>
|
||||
<span class="device-state" id="roofState">READY</span>
|
||||
<span class="device-state" id="roofState">UNKNOWN</span>
|
||||
</header>
|
||||
<p class="domain-capability-line"><span>可控参数</span>开关 / 停止 · 位置</p>
|
||||
<div class="domain-summary-visual shade-summary-visual" aria-hidden="true">
|
||||
<span>闭合</span><i><b id="shadePositionMeter"></b></i><span>打开</span>
|
||||
</div>
|
||||
<dl class="domain-readouts">
|
||||
<div><dt>遮阳帘</dt><dd id="shadePositionValue">0%</dd></div>
|
||||
<div><dt>动作</dt><dd id="shadeMotionState">停止</dd></div>
|
||||
<div><dt>遮阳帘</dt><dd id="shadePositionValue">未知</dd></div>
|
||||
<div><dt>动作</dt><dd id="shadeMotionState">未知</dd></div>
|
||||
<div><dt>初始化</dt><dd id="shadeInitializedState">未知</dd></div>
|
||||
<div><dt>热保护</dt><dd id="shadeThermalState">正常</dd></div>
|
||||
<div><dt>热保护</dt><dd id="shadeThermalState">未知</dd></div>
|
||||
</dl>
|
||||
</article>
|
||||
|
||||
@@ -339,7 +266,7 @@
|
||||
<header>
|
||||
<span class="domain-icon"><i data-lucide="lamp-ceiling"></i></span>
|
||||
<div><strong>车内灯光</strong><small>INTERIOR / AMBIENT · CH1</small></div>
|
||||
<span class="device-state" id="ambientPower">OFF</span>
|
||||
<span class="device-state" id="ambientPower">UNKNOWN</span>
|
||||
</header>
|
||||
<p class="domain-capability-line"><span>可控参数</span>阅读灯 · 氛围亮度 · 色号</p>
|
||||
<div class="domain-summary-visual ambient-summary-visual" aria-hidden="true">
|
||||
@@ -347,9 +274,9 @@
|
||||
<span data-ambient-zone="rear"><i id="ambientRearMeter"></i><b>后排</b></span>
|
||||
</div>
|
||||
<dl class="domain-readouts">
|
||||
<div><dt>氛围灯</dt><dd id="ambientMode">关闭</dd></div>
|
||||
<div><dt>室内/阅读灯</dt><dd id="interiorLightPower">关闭</dd></div>
|
||||
<div><dt>氛围亮度</dt><dd id="ambientBrightness">0 / 0</dd></div>
|
||||
<div><dt>氛围灯</dt><dd id="ambientMode">未知</dd></div>
|
||||
<div><dt>室内/阅读灯</dt><dd id="interiorLightPower">未知</dd></div>
|
||||
<div><dt>氛围亮度</dt><dd id="ambientBrightness">- / -</dd></div>
|
||||
<div><dt>氛围色号</dt><dd id="ambientColorIndex">- / -</dd></div>
|
||||
</dl>
|
||||
</article>
|
||||
@@ -358,7 +285,7 @@
|
||||
<header>
|
||||
<span class="domain-icon"><i data-lucide="gallery-vertical-end"></i></span>
|
||||
<div><strong>车窗</strong><small>CMD 0x431 · FB 0x388 · CH1</small></div>
|
||||
<span class="device-state" id="windowState">READY</span>
|
||||
<span class="device-state" id="windowState">UNKNOWN</span>
|
||||
</header>
|
||||
<p class="domain-capability-line"><span>可控参数</span>四窗独立 · 开/关/停 · 位置</p>
|
||||
<div class="domain-summary-visual window-summary-visual" aria-hidden="true">
|
||||
@@ -368,18 +295,18 @@
|
||||
<span data-window-meter="rear_right"><i></i><b>右后</b></span>
|
||||
</div>
|
||||
<dl class="domain-readouts">
|
||||
<div><dt>左前窗</dt><dd id="frontLeftWindow">0%</dd></div>
|
||||
<div><dt>右前窗</dt><dd id="frontRightWindow">0%</dd></div>
|
||||
<div><dt>左后窗</dt><dd id="rearLeftWindow">0%</dd></div>
|
||||
<div><dt>右后窗</dt><dd id="rearRightWindow">0%</dd></div>
|
||||
<div><dt>左前窗</dt><dd id="frontLeftWindow">未知</dd></div>
|
||||
<div><dt>右前窗</dt><dd id="frontRightWindow">未知</dd></div>
|
||||
<div><dt>左后窗</dt><dd id="rearLeftWindow">未知</dd></div>
|
||||
<div><dt>右后窗</dt><dd id="rearRightWindow">未知</dd></div>
|
||||
</dl>
|
||||
</article>
|
||||
|
||||
<article class="domain-panel lighting-panel" data-domain-panel="front_light" data-device-channel="ch1">
|
||||
<header>
|
||||
<span class="domain-icon"><i data-lucide="car-front"></i></span>
|
||||
<div><strong>前大灯</strong><small>FRONT LIGHT · SWITCH</small></div>
|
||||
<span class="device-state" id="frontLightState">OFF</span>
|
||||
<div><strong>前大灯</strong><small>FB 0x38A · CH1</small></div>
|
||||
<span class="device-state" id="frontLightState">UNKNOWN</span>
|
||||
</header>
|
||||
<p class="domain-capability-line"><span>可控参数</span>近/远光 · 雾灯 · 转向灯</p>
|
||||
<div class="domain-summary-visual lighting-summary-visual" aria-hidden="true">
|
||||
@@ -389,10 +316,10 @@
|
||||
<span data-light-meter="turnSignalState"><i></i><b>转向</b></span>
|
||||
</div>
|
||||
<dl class="domain-readouts">
|
||||
<div><dt>近光灯</dt><dd id="lowBeamState">关闭</dd></div>
|
||||
<div><dt>远光灯</dt><dd id="highBeamState">关闭</dd></div>
|
||||
<div><dt>雾灯</dt><dd id="fogLightState">关闭</dd></div>
|
||||
<div><dt>转向灯</dt><dd id="turnSignalState">关闭</dd></div>
|
||||
<div><dt>近光灯</dt><dd id="lowBeamState">未知</dd></div>
|
||||
<div><dt>远光灯</dt><dd id="highBeamState">未知</dd></div>
|
||||
<div><dt>雾灯</dt><dd id="fogLightState">未知</dd></div>
|
||||
<div><dt>转向灯</dt><dd id="turnSignalState">未知</dd></div>
|
||||
</dl>
|
||||
</article>
|
||||
|
||||
@@ -476,11 +403,6 @@
|
||||
<div class="dynamic-mode-state" data-dynamic-mode>
|
||||
<i data-lucide="waves"></i><span>动态模式</span><b>未运行</b>
|
||||
</div>
|
||||
<div class="thermal-states">
|
||||
<span data-function="ventilation"><i data-lucide="wind"></i>通风 <b>--</b></span>
|
||||
<span data-function="heating"><i data-lucide="flame"></i>加热 <b>--</b></span>
|
||||
<span data-function="massage"><i data-lucide="waves"></i>按摩 <b>--</b></span>
|
||||
</div>
|
||||
<div class="motion-state-grid" aria-live="polite"></div>
|
||||
</article>
|
||||
<article class="seat-card" data-seat="driver">
|
||||
@@ -576,7 +498,6 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div class="gesture-backdrop" id="gestureBackdrop" hidden></div>
|
||||
<aside class="gesture-drawer" id="gestureDrawer" aria-hidden="true" aria-labelledby="gestureDrawerTitle">
|
||||
<header class="gesture-drawer-header">
|
||||
<div>
|
||||
@@ -646,6 +567,51 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="mode-editor-backdrop" id="modeEditorBackdrop" hidden>
|
||||
<section class="mode-editor" role="dialog" aria-modal="true" aria-labelledby="modeEditorTitle" aria-describedby="modeEditorDescription">
|
||||
<div class="mode-editor-setup">
|
||||
<header class="mode-editor-header">
|
||||
<div>
|
||||
<span>MODE WORKFLOW</span>
|
||||
<h2 id="modeEditorTitle">编辑功能模式</h2>
|
||||
<p id="modeEditorDescription">执行后生成 CAN 控制指令流;完成后永久保存到当前模式卡片。</p>
|
||||
</div>
|
||||
<button id="modeEditorClose" type="button" aria-label="关闭模式编辑器"><i data-lucide="x"></i></button>
|
||||
</header>
|
||||
<div class="mode-editor-inputs">
|
||||
<label class="mode-editor-field mode-editor-name-field" for="modeEditorName">
|
||||
<span>模式名称</span>
|
||||
<input id="modeEditorName" maxlength="64" autocomplete="off" placeholder="输入模式名称" />
|
||||
</label>
|
||||
<label class="mode-editor-field mode-editor-instruction-field" for="modeEditorInstruction">
|
||||
<span>自然语言指令</span>
|
||||
<textarea id="modeEditorInstruction" maxlength="1000" rows="3" placeholder="例如:空调24度、风量2档,打开遮阳帘"></textarea>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<section class="mode-editor-flow" aria-live="polite">
|
||||
<header>
|
||||
<span><i data-lucide="workflow"></i>CAN 控制指令流</span>
|
||||
<div class="mode-editor-flow-tools">
|
||||
<b id="modeEditorFlowCount">尚未生成</b>
|
||||
<button id="modeEditorFlowExpand" type="button" aria-expanded="false" aria-label="展开 CAN 控制指令区域" title="展开 CAN 控制指令区域">
|
||||
<i data-lucide="maximize-2"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="mode-editor-flow-preview empty" id="modeEditorFlowPreview">
|
||||
<p>点击“执行”后,这里会显示由后端生成的 CAN 控制指令流。</p>
|
||||
</div>
|
||||
</section>
|
||||
<p class="mode-editor-status" id="modeEditorStatus">修改自然语言后需要重新执行,才能点击完成永久保存。</p>
|
||||
<footer class="mode-editor-actions">
|
||||
<button id="modeEditorCancel" type="button">取消</button>
|
||||
<button class="mode-editor-execute" id="modeEditorExecute" type="button"><i data-lucide="play"></i><span>执行</span></button>
|
||||
<button class="mode-editor-complete" id="modeEditorComplete" type="button" disabled><i data-lucide="save"></i><span>完成</span></button>
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast" role="status" aria-live="polite"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<meta name="color-scheme" content="light" />
|
||||
<title>登录 · Cognitive Agent</title>
|
||||
<link rel="stylesheet" href="/login.css" />
|
||||
<script src="/network.js?v=android-pad-1" defer></script>
|
||||
<script src="/network.js?v=android-pad-2" defer></script>
|
||||
<script src="/login.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -12,30 +12,11 @@
|
||||
return { ...state };
|
||||
}
|
||||
|
||||
function renderBadge() {
|
||||
const badge = document.querySelector("#connectionBadge");
|
||||
if (!badge) return;
|
||||
const presentation = {
|
||||
connecting: ["neutral", "loader-circle", "连接中"],
|
||||
online: ["", "wifi", "服务已连接"],
|
||||
degraded: ["warning", "wifi-low", "连接不稳定"],
|
||||
offline: ["danger", "wifi-off", "服务已断开"],
|
||||
}[state.status];
|
||||
badge.className = `status-badge connection-badge ${presentation[0]}`.trim();
|
||||
badge.innerHTML = `<i data-lucide="${presentation[1]}"></i><span>${presentation[2]}</span>`;
|
||||
badge.title = state.lastError || presentation[2];
|
||||
window.lucide?.createIcons?.({
|
||||
attrs: { "aria-hidden": "true" },
|
||||
nodes: [badge],
|
||||
});
|
||||
}
|
||||
|
||||
function publish(status, error = null) {
|
||||
const changed = state.status !== status || state.lastError !== error;
|
||||
state.status = status;
|
||||
state.lastError = error;
|
||||
if (status === "online") state.lastSuccessAt = new Date().toISOString();
|
||||
renderBadge();
|
||||
if (!changed) return;
|
||||
const detail = snapshot();
|
||||
listeners.forEach((listener) => listener(detail));
|
||||
@@ -110,7 +91,6 @@
|
||||
|
||||
function startMonitoring() {
|
||||
if (probeTimer !== null) return;
|
||||
renderBadge();
|
||||
probe();
|
||||
probeTimer = window.setInterval(probe, 10000);
|
||||
window.addEventListener("online", () => {
|
||||
|
||||
@@ -4,12 +4,8 @@
|
||||
}
|
||||
|
||||
function showUpdate(registration) {
|
||||
const badge = document.querySelector("#pwaBadge");
|
||||
if (!badge) return;
|
||||
badge.hidden = false;
|
||||
badge.innerHTML = '<i data-lucide="refresh-cw"></i><span>发现更新</span>';
|
||||
badge.onclick = () => registration.waiting?.postMessage({ type: "SKIP_WAITING" });
|
||||
window.lucide?.createIcons?.({ attrs: { "aria-hidden": "true" }, nodes: [badge] });
|
||||
notify("检测到前端更新,正在刷新页面");
|
||||
registration.waiting?.postMessage({ type: "SKIP_WAITING" });
|
||||
}
|
||||
|
||||
async function register() {
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.console-v2[data-module="self-extension"] .v2-control-section,
|
||||
.console-v2[data-module="self-extension"] .v2-config-section,
|
||||
.console-v2[data-module="self-extension"] .v2-sidebar > .v2-sidebar-footer:not(.v2-extension-footer),
|
||||
.console-v2[data-module="self-extension"] .v2-operation-shell,
|
||||
.console-v2[data-module="self-extension"] .v2-output-shell {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
const CACHE_VERSION = "cognitive-agent-pad-v179";
|
||||
const CACHE_VERSION = "cognitive-agent-pad-v202";
|
||||
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
|
||||
const RUNTIME_CACHE = `${CACHE_VERSION}-runtime`;
|
||||
const APP_SHELL = [
|
||||
"/",
|
||||
"/index.html",
|
||||
"/styles.css?v=dual-seat-10",
|
||||
"/network.js?v=android-pad-1",
|
||||
"/app.js?v=dual-seat-90",
|
||||
"/gesture-control.js?v=android-pad-8",
|
||||
"/pwa.js?v=android-pad-2",
|
||||
"/console-v2.css?v=sync-layout-27",
|
||||
"/console-v2.js?v=sync-layout-27",
|
||||
"/styles.css?v=dual-seat-16",
|
||||
"/network.js?v=android-pad-2",
|
||||
"/app.js?v=dual-seat-101",
|
||||
"/gesture-control.js?v=android-pad-9",
|
||||
"/pwa.js?v=android-pad-3",
|
||||
"/console-v2.css?v=sync-layout-40",
|
||||
"/console-v2.js?v=sync-layout-41",
|
||||
"/self-extension.css?v=sync-layout-26",
|
||||
"/self-extension.js?v=sync-layout-26",
|
||||
"/assets/pets/self-extension-cat.png?v=3",
|
||||
|
||||
+572
-477
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user