feat: integrate calibration extension and self-repair modules
This commit is contained in:
+236
-27
@@ -11,12 +11,23 @@
|
||||
|
||||
const $ = (selector, root = document) => root.querySelector(selector);
|
||||
const $$ = (selector, root = document) => [...root.querySelectorAll(selector)];
|
||||
const initialRole = sessionStorage.getItem("cognitive-agent.demo-role") === "admin"
|
||||
? "admin"
|
||||
: "user";
|
||||
const storedModule = sessionStorage.getItem("cognitive-agent.active-module");
|
||||
|
||||
const ui = {
|
||||
role: sessionStorage.getItem("cognitive-agent.demo-role") === "admin" ? "admin" : "user",
|
||||
role: initialRole,
|
||||
controlMode: sessionStorage.getItem("cognitive-agent.control-mode") === "gesture"
|
||||
? "gesture"
|
||||
: "natural",
|
||||
activeModule: storedModule === "self-extension"
|
||||
? "self-extension"
|
||||
: storedModule === "self-calibration"
|
||||
? "self-calibration"
|
||||
: storedModule === "self-repair"
|
||||
? "self-repair"
|
||||
: "self-orchestration",
|
||||
activePage: 0,
|
||||
sidebarCollapsed: sessionStorage.getItem("cognitive-agent.sidebar-collapsed") === "true",
|
||||
};
|
||||
@@ -159,6 +170,9 @@
|
||||
<option value="admin">admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="v2-extension-profile-title" aria-label="自扩展面向所有登录用户">
|
||||
<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>
|
||||
@@ -177,7 +191,7 @@
|
||||
|
||||
const config = make("section", "v2-sidebar-section v2-config-section", `
|
||||
<div class="v2-divider"></div>
|
||||
<span class="v2-field-caption">配置中心</span>
|
||||
<span class="v2-field-caption">配置信息</span>
|
||||
<div class="v2-config-stack" id="v2ConfigStack"></div>`);
|
||||
|
||||
const footer = make("footer", "v2-sidebar-footer", `
|
||||
@@ -194,33 +208,44 @@
|
||||
const vehicleField = $(".vehicle-field", originalForm);
|
||||
const providerField = $(".field-group:has(.segmented [data-provider])", originalForm);
|
||||
const executionField = $(".field-group:has(.execution-mode-options)", originalForm);
|
||||
const hardwareAccessField = $("#hardwareAccessField", originalForm);
|
||||
const simulationFaultField = $("#simulationFaultField", originalForm);
|
||||
const approvalField = $("#approvalField", 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);
|
||||
}
|
||||
|
||||
const modelField = make("div", "v2-config-field v2-model-field", `
|
||||
<span class="field-label">推理模型</span>
|
||||
<div class="v2-static-select" id="v2ModelField">
|
||||
<i data-lucide="sparkles"></i>
|
||||
<strong id="v2ModelValue">模型检测中</strong>
|
||||
<span class="v2-field-state" id="v2ModelState">AUTO</span>
|
||||
</div>
|
||||
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);
|
||||
|
||||
const gestureModelField = make("div", "v2-config-field v2-gesture-model-field", `
|
||||
<span class="field-label">手势模型</span>
|
||||
<div class="v2-static-select" id="v2GestureModelField">
|
||||
<i data-lucide="scan-line"></i>
|
||||
<strong id="v2GestureModelValue">MediaPipe GR</strong>
|
||||
<span class="v2-field-state">VISION</span>
|
||||
</div>
|
||||
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", `
|
||||
@@ -234,14 +259,16 @@
|
||||
}
|
||||
|
||||
const executionSelect = make("div", "v2-config-field v2-execution-field", `
|
||||
<label class="field-label" for="v2ExecutionSelect">执行目标</label>
|
||||
<div class="v2-select-shell">
|
||||
<div class="v2-config-card v2-config-select-card">
|
||||
<i data-lucide="workflow"></i>
|
||||
<select id="v2ExecutionSelect" aria-label="选择执行目标">
|
||||
<option value="dry_run">虚拟 CAN</option>
|
||||
<option value="simulated_hardware">模拟台架</option>
|
||||
<option value="hardware">真实台架</option>
|
||||
</select>
|
||||
<div class="v2-config-card-copy"><small>执行目标</small><div class="v2-config-card-select">
|
||||
<select id="v2ExecutionSelect" aria-label="选择执行目标">
|
||||
<option value="dry_run">虚拟 CAN</option>
|
||||
<option value="simulated_hardware">模拟台架</option>
|
||||
<option value="hardware">真实台架</option>
|
||||
</select>
|
||||
</div></div>
|
||||
<span class="v2-field-state">ADMIN</span>
|
||||
</div>
|
||||
<p class="provider-note" id="v2ExecutionNote">管理员可切换执行环境</p>`);
|
||||
configStack.append(executionSelect);
|
||||
@@ -253,7 +280,7 @@
|
||||
}
|
||||
|
||||
const advanced = make("div", "v2-advanced-stack", "");
|
||||
[hardwareAccessField, simulationFaultField, approvalField].forEach((field) => {
|
||||
[simulationFaultField, approvalField].forEach((field) => {
|
||||
if (!field) return;
|
||||
field.classList.add("v2-config-field", "v2-advanced-field");
|
||||
advanced.append(field);
|
||||
@@ -281,6 +308,7 @@
|
||||
}
|
||||
|
||||
const naturalGrid = make("div", "v2-natural-grid", "");
|
||||
naturalGrid.id = "v2NaturalGrid";
|
||||
originalForm.classList.add("v2-request-form");
|
||||
naturalGrid.append(originalForm);
|
||||
if (requestHeader) {
|
||||
@@ -303,17 +331,81 @@
|
||||
gestureDrawer.classList.add("v2-inline-gesture");
|
||||
const cameraStage = $(".gesture-camera-stage", gestureDrawer);
|
||||
const cameraToggle = $("#gestureStartButton", gestureDrawer);
|
||||
if (cameraStage && cameraToggle) {
|
||||
const debugToggle = $("#gestureArmButton", gestureDrawer);
|
||||
const actionRail = $(".gesture-actions", gestureDrawer);
|
||||
const statusStrip = $(".gesture-status-strip", gestureDrawer);
|
||||
const gestureNow = $(".gesture-now", gestureDrawer);
|
||||
const guideCard = $(".gesture-guide-card", gestureDrawer);
|
||||
if (cameraToggle) {
|
||||
cameraToggle.classList.add("v2-camera-toggle");
|
||||
cameraToggle.setAttribute("aria-label", "开启摄像头");
|
||||
cameraToggle.setAttribute("title", "开启摄像头");
|
||||
cameraStage.append(cameraToggle);
|
||||
}
|
||||
if (actionRail && cameraToggle && debugToggle) {
|
||||
actionRail.classList.add("v2-gesture-action-rail");
|
||||
actionRail.insertBefore(debugToggle, cameraToggle);
|
||||
actionRail.append(cameraToggle);
|
||||
}
|
||||
|
||||
const resultStack = make("div", "v2-gesture-result-stack", "");
|
||||
resultStack.id = "v2GestureResultStack";
|
||||
const recognitionCard = make("section", "v2-gesture-recognition-card", `
|
||||
<span class="v2-gesture-result-label">手势识别结果</span>`);
|
||||
const executionSlot = make("section", "v2-gesture-execution-slot", `
|
||||
<span class="v2-gesture-result-label">执行结果</span>`);
|
||||
executionSlot.id = "v2GestureExecutionSlot";
|
||||
if (statusStrip) recognitionCard.append(statusStrip);
|
||||
if (gestureNow) recognitionCard.append(gestureNow);
|
||||
resultStack.append(recognitionCard, executionSlot);
|
||||
|
||||
if (cameraStage) gestureDrawer.append(cameraStage);
|
||||
if (actionRail) gestureDrawer.append(actionRail);
|
||||
gestureDrawer.append(resultStack);
|
||||
if (guideCard) gestureDrawer.append(guideCard);
|
||||
gesturePanel.append(gestureDrawer);
|
||||
}
|
||||
return operation;
|
||||
}
|
||||
|
||||
function buildModuleSwitcher(mainPanel) {
|
||||
const modules = [
|
||||
{ id: "self-orchestration", label: "自编排", available: true },
|
||||
{
|
||||
id: "self-calibration",
|
||||
label: "自标定",
|
||||
available: true,
|
||||
adminOnly: true,
|
||||
},
|
||||
{ id: "self-extension", label: "自扩展", available: true },
|
||||
{
|
||||
id: "self-repair",
|
||||
label: "自修复",
|
||||
available: true,
|
||||
adminOnly: true,
|
||||
},
|
||||
];
|
||||
const switcher = make("nav", "v2-module-switcher", "");
|
||||
switcher.id = "v2ModuleSwitcher";
|
||||
switcher.setAttribute("role", "tablist");
|
||||
switcher.setAttribute("aria-label", "能力模块切换");
|
||||
modules.forEach((module, index) => {
|
||||
const button = make("button", "v2-module-tab", `<span>${module.label}</span>`);
|
||||
button.type = "button";
|
||||
button.id = `v2ModuleTab${index}`;
|
||||
button.dataset.module = module.id;
|
||||
button.setAttribute("role", "tab");
|
||||
button.setAttribute("aria-selected", "false");
|
||||
if (module.adminOnly) button.dataset.adminOnly = "true";
|
||||
if (!module.available) {
|
||||
button.disabled = true;
|
||||
button.setAttribute("aria-disabled", "true");
|
||||
button.title = module.adminOnly ? `${module.label}仅对 admin 开放` : `${module.label}模块待接入`;
|
||||
}
|
||||
switcher.append(button);
|
||||
});
|
||||
mainPanel.prepend(switcher);
|
||||
}
|
||||
|
||||
function buildOutput(mainPanel) {
|
||||
const output = make("section", "v2-output-shell", `
|
||||
<header class="v2-output-heading">
|
||||
@@ -436,6 +528,13 @@
|
||||
ui.controlMode = mode === "gesture" ? "gesture" : "natural";
|
||||
sessionStorage.setItem("cognitive-agent.control-mode", ui.controlMode);
|
||||
document.body.dataset.controlMode = ui.controlMode;
|
||||
const requestStatus = $(".v2-request-status");
|
||||
const statusTarget = ui.controlMode === "gesture"
|
||||
? $("#v2GestureExecutionSlot")
|
||||
: $("#v2NaturalGrid");
|
||||
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);
|
||||
@@ -456,7 +555,52 @@
|
||||
}
|
||||
}
|
||||
|
||||
function setModule(module, { persist = true } = {}) {
|
||||
if (["self-calibration", "self-repair"].includes(module) && ui.role !== "admin") {
|
||||
setRole("admin");
|
||||
}
|
||||
const allowed = new Set(["self-orchestration", "self-extension"]);
|
||||
if (ui.role === "admin") {
|
||||
allowed.add("self-calibration");
|
||||
allowed.add("self-repair");
|
||||
}
|
||||
ui.activeModule = allowed.has(module) ? module : "self-orchestration";
|
||||
if (persist) {
|
||||
sessionStorage.setItem("cognitive-agent.active-module", ui.activeModule);
|
||||
}
|
||||
document.body.dataset.module = ui.activeModule;
|
||||
const roleSelect = $("#v2RoleSelect");
|
||||
if (roleSelect) {
|
||||
roleSelect.disabled = ui.activeModule === "self-repair";
|
||||
dropdownControls.get(roleSelect)?.sync();
|
||||
}
|
||||
$$('.v2-module-tab').forEach((button) => {
|
||||
const active = button.dataset.module === ui.activeModule;
|
||||
button.classList.toggle("active", active);
|
||||
button.setAttribute("aria-selected", String(active));
|
||||
button.tabIndex = active ? 0 : -1;
|
||||
});
|
||||
if (["self-extension", "self-calibration", "self-repair"].includes(ui.activeModule)) {
|
||||
const drawer = $("#gestureDrawer");
|
||||
if (drawer?.classList.contains("open")) $("#gestureCloseButton")?.click();
|
||||
} else {
|
||||
setControlMode(ui.controlMode, { triggerGesture: false });
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent("cognitive-agent:module-change", {
|
||||
detail: { module: ui.activeModule },
|
||||
}));
|
||||
window.setTimeout(() => window.dispatchEvent(new Event("resize")), 120);
|
||||
}
|
||||
|
||||
function setRole(role) {
|
||||
if (ui.activeModule === "self-repair" && role !== "admin") {
|
||||
const roleSelect = $("#v2RoleSelect");
|
||||
if (roleSelect) {
|
||||
roleSelect.value = "admin";
|
||||
dropdownControls.get(roleSelect)?.sync();
|
||||
}
|
||||
return;
|
||||
}
|
||||
ui.role = role === "admin" ? "admin" : "user";
|
||||
sessionStorage.setItem("cognitive-agent.demo-role", ui.role);
|
||||
document.body.dataset.demoRole = ui.role;
|
||||
@@ -466,7 +610,27 @@
|
||||
$("#v2RoleSelect").value = ui.role;
|
||||
dropdownControls.get($("#v2RoleSelect"))?.sync();
|
||||
$("#v2PageRail").hidden = ui.role !== "admin";
|
||||
const calibrationTab = $('.v2-module-tab[data-module="self-calibration"]');
|
||||
if (calibrationTab) {
|
||||
calibrationTab.disabled = false;
|
||||
calibrationTab.setAttribute("aria-disabled", "false");
|
||||
calibrationTab.title = ui.role === "admin"
|
||||
? ""
|
||||
: "进入自标定时将自动切换为 admin";
|
||||
}
|
||||
const repairTab = $('.v2-module-tab[data-module="self-repair"]');
|
||||
if (repairTab) {
|
||||
repairTab.hidden = false;
|
||||
repairTab.disabled = false;
|
||||
repairTab.setAttribute("aria-disabled", "false");
|
||||
repairTab.title = ui.role === "admin"
|
||||
? ""
|
||||
: "进入自修复时将自动切换为 admin";
|
||||
}
|
||||
if (ui.role !== "admin") {
|
||||
if (["self-calibration", "self-repair"].includes(ui.activeModule)) {
|
||||
setModule("self-orchestration");
|
||||
}
|
||||
setPage(0);
|
||||
requestUserHardwareMode();
|
||||
} else {
|
||||
@@ -558,11 +722,42 @@
|
||||
if (ui.role === "user") requestUserHardwareMode();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
$("#v2RoleSelect")?.addEventListener("change", (event) => setRole(event.target.value));
|
||||
$$('[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();
|
||||
@@ -571,6 +766,10 @@
|
||||
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);
|
||||
});
|
||||
$("#v2SidebarToggle")?.addEventListener(
|
||||
"click",
|
||||
() => setSidebarCollapsed(!ui.sidebarCollapsed),
|
||||
@@ -645,6 +844,7 @@
|
||||
requestHeader,
|
||||
gestureDrawer,
|
||||
);
|
||||
buildModuleSwitcher(mainPanel);
|
||||
|
||||
$(".pipeline", mainPanel)?.classList.add("v2-legacy-hidden");
|
||||
$(".topbar")?.classList.add("v2-legacy-hidden");
|
||||
@@ -652,10 +852,12 @@
|
||||
buildOutput(mainPanel);
|
||||
bindEvents();
|
||||
setControlMode(ui.controlMode, { triggerGesture: false });
|
||||
setModule(ui.activeModule, { persist: false });
|
||||
setRole(ui.role);
|
||||
setSidebarCollapsed(ui.sidebarCollapsed);
|
||||
setPage(0);
|
||||
syncRuntimePresentation();
|
||||
refreshVehicleLinkState();
|
||||
window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } });
|
||||
requestAnimationFrame(() => {
|
||||
updateModeEdgeHints();
|
||||
@@ -663,5 +865,12 @@
|
||||
});
|
||||
}
|
||||
|
||||
window.CognitiveAgentConsoleV2 = {
|
||||
enhanceSelect,
|
||||
getModule: () => ui.activeModule,
|
||||
getRole: () => ui.role,
|
||||
setModule,
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
}());
|
||||
|
||||
Reference in New Issue
Block a user