(function () { "use strict"; const API_ROOT = "/api/self-repair"; const VIEW_KEY = "cognitive-agent.self-repair-view"; const MODE_KEY = "cognitive-agent.self-repair-execution"; const PHASE_INDEX = { IDLE: -1, BASELINE: 0, INJECTED: 1, DIAGNOSED: 2, REPAIRING: 3, VERIFYING: 4, PASSED: 5, FAILED: 2, }; const STATE_TEXT = { OK: "正常", WARN: "告警", FAULT: "故障", UNKNOWN: "未检测", UNMONITORED: "关联待测", NOT_USED: "未启用", IDLE: "检测中", }; const TIMELINE = [ { title: "开始正常任务", phases: ["BASELINE"] }, { title: "故障发生并停止", phases: ["INJECTED"] }, { title: "AI判断原因", phases: ["DIAGNOSED"] }, { title: "执行恢复策略", phases: ["REPAIRING"] }, { title: "继续原任务", phases: ["VERIFYING", "PASSED"] }, ]; const $ = (selector, root = document) => root.querySelector(selector); const $$ = (selector, root = document) => [...root.querySelectorAll(selector)]; const esc = (value) => String(value == null ? "" : value) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """); let latest = null; let selectedCaseId = null; let selectedPinId = "JC1-1"; let activeView = sessionStorage.getItem(VIEW_KEY) === "hardware" ? "hardware" : "software"; let executionMode = sessionStorage.getItem(MODE_KEY) === "bench" ? "bench" : "shadow"; let polling = false; let toastTimer = 0; let confirmationResolve = null; function isAdmin() { return window.CognitiveAgentConsoleV2?.getRole() === "admin" || document.body.dataset.demoRole === "admin"; } function isActive() { return isAdmin() && document.body.dataset.module === "self-repair"; } function toast(message, error = false) { const node = $("#toast"); if (!node) return; node.textContent = message; node.className = error ? "toast show error" : "toast show"; window.clearTimeout(toastTimer); toastTimer = window.setTimeout(() => { node.className = "toast"; }, 3600); } function buildShell() { const sidebar = $(".v2-sidebar"); const mainPanel = $(".main-panel"); const moduleSwitcher = $("#v2ModuleSwitcher"); if (!sidebar || !mainPanel || !moduleSwitcher || $("#v2SelfRepair")) return false; const repairSidebar = document.createElement("section"); repairSidebar.className = "v2-sidebar-section v2-repair-sidebar"; repairSidebar.innerHTML = `
SELF-REPAIR DESK

控制台

修复对象
配置信息
当前车型腾势 N7
N7
推理模型GPT-5.6-sol
READY
座椅控制器T2G 4BF
4BF
验证方式
SAFE

影子仿真不会写入 ECU 或驱动实体设备

`; sidebar.insertBefore(repairSidebar, $(".v2-sidebar-footer")); const repairFooter = document.createElement("footer"); repairFooter.className = "v2-sidebar-footer v2-repair-footer offline"; repairFooter.innerHTML = `
自修复安全链路CAN 桥接离线
`; sidebar.append(repairFooter); const root = document.createElement("section"); root.className = "v2-self-repair"; root.id = "v2SelfRepair"; root.setAttribute("aria-label", "座椅 AI 自修复验证台"); root.innerHTML = `
COGNITIVE SEAT / SELF-REPAIR

座椅AI自修复验证台

量产软件故障闭环恢复 · 开发板接口在线诊断

CAN桥接离线
座椅ECU未检测
AI状态等待验证
01

软件自修复

正常任务 → 故障停止 → AI 判断 → 安全修复 → 继续原目标

PRODUCTION CASES

量产故障用例

0 / 6

等待启动批量验证

DIGITAL TWIN

座椅3D孪生

正常基线
正在构建座椅孪生坐垫 · 靠背 · 腿托 · 滑轨
拖动旋转 · 滚轮缩放 · 双击复位
影子动作仿真等待启动座椅任务待机

实际动作将同步显示在座椅模型

HEALING FLOW

座椅任务自修复

0.000 s
软件自修复验证记录成功用例自动保存诊断快照
硬件自诊断记录支路状态变化与最终报告
确认台架安全条件确认后才会向实物链路提交请求

`; moduleSwitcher.insertAdjacentElement("afterend", root); const select = $("#repairExecutionMode"); select.value = executionMode; window.CognitiveAgentConsoleV2?.enhanceSelect(select); return true; } async function apiPost(path, body = {}) { const response = await fetch(`${API_ROOT}${path}`, { method: "POST", credentials: "same-origin", headers: { Accept: "application/json", "Content-Type": "application/json", "X-Cognitive-Agent-Role": "admin", }, body: JSON.stringify(body), }); const value = await response.json().catch(() => ({ message: `HTTP ${response.status}` })); if (!response.ok || value.ok === false) { throw new Error(value.message || value.error || `HTTP ${response.status}`); } return value; } function showView(view) { activeView = view === "hardware" ? "hardware" : "software"; sessionStorage.setItem(VIEW_KEY, activeView); $$('[data-repair-view]').forEach((button) => { const active = button.dataset.repairView === activeView; button.classList.toggle("active", active); button.setAttribute("aria-pressed", String(active)); }); $$(".v2-repair-view").forEach((page) => { const active = page.dataset.repairPage === activeView; page.hidden = !active; page.classList.toggle("active", active); }); window.setTimeout(() => { window.dispatchEvent(new Event("resize")); if (activeView === "software") { window.dispatchEvent(new Event("self-repair-seat-reset")); } }, 80); } function scenarioForDisplay() { if (!latest?.scenarios?.length) return null; return latest.scenarios.find((item) => item.id === selectedCaseId) || latest.scenarios[0]; } function timelineDetail(index, scenario, healing) { const telemetry = healing.telemetry || {}; const details = [ scenario.normal_task, telemetry.bench_reaction || scenario.fault_reaction, scenario.diagnosis, scenario.repair, scenario.recovery_reaction, ]; const phases = TIMELINE[index].phases; const event = [...(healing.events || [])].reverse() .find((item) => phases.includes(item.phase)); return event?.detail || event?.title || details[index] || "等待运行日志"; } function renderTimeline() { const healing = latest.healing; const scenario = scenarioForDisplay(); if (!scenario) return; const phaseIndex = PHASE_INDEX[healing.phase] ?? -1; const passed = healing.phase === "PASSED"; const failed = healing.phase === "FAILED"; $("#repairTimeline").innerHTML = TIMELINE.map((stage, index) => { let state = "pending"; if (passed || phaseIndex > index) state = "complete"; else if (phaseIndex === index && healing.phase !== "IDLE") state = failed ? "failed" : "active"; const label = state === "complete" ? "已完成" : state === "active" ? "进行中" : state === "failed" ? "失败" : "等待"; return `
${esc(stage.title)}${label}

${esc(timelineDetail(index, scenario, healing))}

`; }).join(""); $("#repairElapsed").textContent = `${Number(healing.elapsed_s || 0).toFixed(3)} s`; } function renderCases() { if (!selectedCaseId && latest.scenarios?.length) selectedCaseId = latest.scenarios[0].id; const results = new Map((latest.batch?.results || []).map((item) => [item.case_id, item])); $("#repairCaseList").innerHTML = (latest.scenarios || []).map((scenario, index) => { const result = results.get(scenario.id); const running = latest.batch?.current_case_id === scenario.id || (latest.healing?.case_id === scenario.id && !["IDLE", "PASSED", "FAILED"].includes(latest.healing.phase)); const state = result?.result === "PASS" ? "complete" : result?.result === "FAIL" ? "failed" : running ? "active" : "pending"; return ``; }).join(""); $$('[data-repair-case]').forEach((button) => { button.addEventListener("click", () => { if (latest.batch?.active) return; selectedCaseId = button.dataset.repairCase; renderCases(); renderTimeline(); renderTwin(); }); }); const batch = latest.batch || {}; const completed = Number(batch.completed || 0); const total = Number(batch.total || 6); $("#repairBatchProgress").style.width = `${Math.min(100, (completed / Math.max(1, total)) * 100)}%`; $("#repairBatchCount").textContent = `${completed} / ${total}`; $("#repairBatchSummary").textContent = batch.active ? `正在验证 ${batch.current_case_id || "下一项"} · 通过 ${batch.passed} · 失败 ${batch.failed}` : completed ? `批量验证完成 · 通过 ${batch.passed} · 失败 ${batch.failed}` : "等待启动批量验证"; } function renderTwin() { const healing = latest.healing; const scenario = scenarioForDisplay(); if (!scenario) return; const telemetry = healing.telemetry || {}; const live = latest.bench_twin || {}; const bench = executionMode === "bench"; const positions = Array.isArray(telemetry.axis_positions) ? telemetry.axis_positions : [35, 42, 48, 20]; const axes = bench && live.axes?.length === 4 ? live.axes : positions.map((position, motor) => ({ motor, label: ["水平滑轨", "坐盆升降", "靠背角度", "腿托角度"][motor], position_pct: Number(position), active: (telemetry.active_axes || []).includes(motor), position_valid: telemetry.position_valid !== false, })); const stage = { NORMAL_TASK: "正常任务", FAULT_REACTION: "故障停止", REPAIR: "执行修复", VERIFYING: "安全复测", RESUMING: "继续原任务", COMPLETE: "任务已恢复", SAFE_STOP: "安全停止", }[telemetry.operation_stage] || healing.phase_text; $("#repairTwinSource").textContent = bench ? (live.online ? "实物 CAN 同步" : "实物 CAN 暂离线") : "影子动作仿真"; $("#repairTwinTask").textContent = healing.phase === "IDLE" ? `等待启动:${scenario.normal_task}` : scenario.normal_task; $("#repairTwinStage").textContent = stage || "待机"; $("#repairTwinState").textContent = healing.phase_text; $("#repairTwinReaction").textContent = telemetry.bench_reaction || (healing.phase === "IDLE" ? scenario.field_basis : scenario.fault_reaction); $("#repairAxisStrip").innerHTML = axes.map((axis) => { const position = Math.max(0, Math.min(100, Number(axis.position_pct || 0))); return `M${axis.motor}${position.toFixed(0)}%`; }).join(""); window.dispatchEvent(new CustomEvent("seat-healing-state", { detail: { state: healing.phase, axes: axes.map((axis) => ({ motor: axis.motor, position_pct: Number(axis.position_pct || 0), active: Boolean(axis.active), valid: axis.position_valid !== false, })), ventilation: Boolean((bench ? live.thermal : telemetry).fan_running), heatingLevel: Number((bench ? live.thermal : telemetry).heater_level || 0), } })); } function connectorRows(name, pins) { if (name === "JA1") return Array.from({ length: 7 }, (_, index) => [index + 1, index + 8]); if (name === "JB1") return Array.from({ length: 9 }, (_, index) => [(index + 1) * 2, index * 2 + 1]); return Array.from({ length: 20 }, (_, index) => [index + 1, index + 21]); } function pinButton(pin) { if (!pin) return ""; const state = pin.enabled === false ? "NOT_USED" : pin.state; return ``; } function renderConnectors(hardware) { const connectors = hardware.connectors || {}; const names = ["JA1", "JB1", "JC1"]; $("#repairConnectors").innerHTML = names.map((name) => { const pins = connectors[name] || []; const byNumber = new Map(pins.map((pin) => [Number(pin.number), pin])); const ok = pins.filter((pin) => pin.state === "OK").length; const abnormal = pins.filter((pin) => ["WARN", "FAULT"].includes(pin.state)).length; return `
${name}已验 ${ok} · 异常 ${abnormal}
${name === "JC1" ? "" : ""}
${connectorRows(name, pins).map((row) => `${pinButton(byNumber.get(row[0]))}${pinButton(byNumber.get(row[1]))}`).join("")}
`; }).join(""); $$('[data-repair-pin]').forEach((button) => button.addEventListener("click", () => { selectedPinId = button.dataset.repairPin; renderHardware(); })); } function renderPinDetail(hardware) { const allPins = Object.values(hardware.connectors || {}).flat(); const pin = allPins.find((item) => item.id === selectedPinId) || allPins[0]; if (!pin) return; selectedPinId = pin.id; const state = pin.enabled === false ? "NOT_USED" : pin.state; $("#repairPinTitle").textContent = pin.id; $("#repairPinState").textContent = STATE_TEXT[state] || state; $("#repairPinState").dataset.state = state; $("#repairPinDetail").innerHTML = `
原理图网络
${esc(pin.net)}
座椅侧功能
${esc(pin.function)}
板内二级溯源
${esc(pin.mcu || "—")}
本工程启用
${pin.enabled === false ? "否" : "是"}

判定依据

建议检查顺序

    ${(pin.actions || ["启动硬件诊断后生成建议"]).map((item) => `
  1. ${esc(item)}
  2. `).join("")}
`; } function renderHardware() { const hardware = latest.hardware || {}; const session = latest.hardware_session || {}; const bridge = latest.bridge || {}; const manager = latest.bridge_manager || {}; const adapterOnline = hardware.link?.adapter === "OK" || Boolean(bridge.bus?.opened) || (bridge.online && bridge.bus_traffic); const layers = [ ["1", "ZCANPRO桥接", bridge.online ? "正常" : "离线", bridge.online ? "OK" : "UNKNOWN"], ["2", "CAN设备 / 通道", adapterOnline ? "设备与通道已打开" : "未连接", adapterOnline ? "OK" : "UNKNOWN"], ["3", "总线实时流量", bridge.bus_traffic ? "正常" : "未检测", bridge.bus_traffic ? "OK" : "WARN"], ["4", "座椅ECU协议", bridge.protocol_online ? "0x301~0x305在线" : "未检测", bridge.protocol_online ? "OK" : "UNKNOWN"], ]; $("#repairLinkLayers").innerHTML = layers.map((item) => `
${item[0]} · ${item[1]}${item[2]}
`).join(""); $("#repairBridgeMessage").textContent = manager.message || "等待桥接状态"; $("#repairLinkDetail").textContent = bridge.protocol_online ? `协议在线 · 最近帧 ${(bridge.recent_frame_ids || []).join(" / ")}` : bridge.bus_traffic ? "CAN 有实时流量,但尚未识别本工程 0x301~0x305 协议。" : "尚未收到实时 CAN 帧,启用针脚保持未检测状态,不表示针脚故障。"; $("#repairHardwareGroups").innerHTML = (hardware.groups || []).map((group) => ``).join(""); $("#repairHardwareOverall").textContent = `已验证 ${hardware.connector_ok_count || 0}/21 · 异常 ${(hardware.connector_fault_count || 0) + (hardware.connector_warn_count || 0)}`; const toggle = $("#repairHardwareToggle"); toggle.innerHTML = session.active ? '停止硬件诊断' : '启动硬件诊断'; renderConnectors(hardware); renderPinDetail(hardware); const events = session.events || []; $("#repairHardwareRecordsBody").innerHTML = `

${esc(session.report_path || "停止诊断后自动保存硬件报告")}

${events.length ? `${[...events].reverse().map((event) => ``).join("")}
时间支路状态摘要
${esc(event.time)}${esc(event.name)}${esc(STATE_TEXT[event.state] || event.state)}${esc(event.summary)}
` : "

尚无硬件诊断事件。

"}`; } function renderGlobal() { const bridge = latest.bridge || {}; const manager = latest.bridge_manager || {}; const healing = latest.healing || {}; const hardwarePhase = latest.hardware_session?.progress?.phase; const integration = latest.integration || {}; $("#repairCanStatus").textContent = bridge.online ? "在线" : "离线"; $("#repairCanDot").dataset.state = bridge.online ? "online" : manager.state === "STARTING" ? "working" : "offline"; $("#repairProtocolStatus").textContent = bridge.protocol_online ? "协议在线" : "未检测"; $("#repairProtocolDot").dataset.state = bridge.protocol_online ? "online" : "offline"; const hardwareState = { BASELINE: ["硬件检测中", "working"], RUNNING: ["硬件检测中", "working"], PASSED: ["21针通过", "online"], FAILED: ["硬件异常", "offline"], }[hardwarePhase]; const aiState = activeView === "hardware" && hardwareState ? hardwareState : [healing.phase_text || "等待验证", healing.phase === "PASSED" ? "online" : healing.phase === "IDLE" ? "offline" : "working"]; $("#repairAiStatus").textContent = aiState[0]; $("#repairAiDot").dataset.state = aiState[1]; $("#repairConnectionText").textContent = bridge.online ? "CAN 桥接在线" : manager.message || "CAN 桥接离线"; $(".v2-repair-footer").classList.toggle("offline", !bridge.online); const benchOption = $('#repairExecutionMode option[value="bench"]'); if (benchOption) benchOption.disabled = !integration.zcanpro_configured; if (executionMode === "bench" && !integration.zcanpro_configured) { executionMode = "shadow"; $("#repairExecutionMode").value = "shadow"; sessionStorage.setItem(MODE_KEY, executionMode); } $("#repairExecutionState").textContent = executionMode === "bench" ? "HIL" : "SAFE"; $("#repairConfigNote").textContent = executionMode === "bench" ? "真实台架动作受本机 admin、安全确认与桥接状态约束" : "影子仿真不会写入 ECU 或驱动实体设备"; $("#repairFirmwareFlash").disabled = !integration.firmware_tools_configured; window.CognitiveAgentConsoleV2?.enhanceSelect($("#repairExecutionMode")); } function renderRecords() { const results = latest.batch?.results || []; const events = latest.healing?.events || []; $("#repairSoftwareRecordsBody").innerHTML = `

${esc(latest.latest_report_path || "成功用例将自动保存诊断快照")}

${results.length ? `${results.map((item) => ``).join("")}
用例结果耗时诊断码
${esc(item.title)}${esc(item.result)}${Number(item.duration_s || 0).toFixed(2)} s${esc(item.diagnosed_code || "—")}
` : "

尚无批量验证记录。

"}
${events.map((event) => `
${esc(event.title)}${esc(event.detail)}
`).join("")}
`; } function renderAll() { renderGlobal(); renderCases(); renderTimeline(); renderTwin(); renderHardware(); renderRecords(); // Polling refreshes data only. Reapplying the active view here would also // reset the user's current 3D orbit every 600 ms. window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } }); } async function poll() { if (!isActive() || polling) return; polling = true; try { const response = await fetch(`${API_ROOT}/state`, { cache: "no-store", credentials: "same-origin", headers: { Accept: "application/json", "X-Cognitive-Agent-Role": "admin" }, }); if (!response.ok) throw new Error(`HTTP ${response.status}`); latest = await response.json(); if (!["IDLE"].includes(latest.healing?.phase)) selectedCaseId = latest.healing.case_id; renderAll(); } catch (error) { $("#repairConnectionText").textContent = `自修复服务连接失败:${error.message}`; $(".v2-repair-footer").classList.add("offline"); } finally { polling = false; } } async function runAction(action, successMessage) { try { await action(); toast(successMessage); await poll(); } catch (error) { toast(error.message, true); } } function finishConfirmation(accepted) { const dialog = $("#repairConfirmDialog"); const resolve = confirmationResolve; confirmationResolve = null; if (dialog?.open) dialog.close(accepted ? "confirmed" : "cancelled"); if (resolve) resolve(accepted); } function confirmAction(message, confirmLabel = "确认执行") { const dialog = $("#repairConfirmDialog"); if (!dialog || confirmationResolve) return Promise.resolve(false); $("#repairConfirmMessage").textContent = message; $("#repairConfirmAccept").textContent = confirmLabel; dialog.showModal(); return new Promise((resolve) => { confirmationResolve = resolve; }); } function bindEvents() { $$('[data-repair-view]').forEach((button) => button.addEventListener("click", () => showView(button.dataset.repairView))); $("#repairExecutionMode").addEventListener("change", (event) => { executionMode = event.target.value === "bench" ? "bench" : "shadow"; sessionStorage.setItem(MODE_KEY, executionMode); if (latest) renderAll(); }); $("#repairStart").addEventListener("click", async () => { if (executionMode === "bench" && !(await confirmAction("即将向实物 ECU 写入受控故障并驱动座椅恢复。请确认运动区域无人、无遮挡且急停可用。"))) return; runAction(() => apiPost("/healing/start", { case_id: selectedCaseId, execution_mode: executionMode }), executionMode === "bench" ? "实物自修复用例已启动" : "影子故障已注入,开始自修复"); }); $("#repairRunAll").addEventListener("click", async () => { if (executionMode === "bench" && !(await confirmAction( "将依次执行6个实物故障用例;每项开始前会自动停止输出并完成四轴归零。请确认运动区域无人、无遮挡且急停可用。", "确认实物批量验证", ))) return; runAction(() => apiPost("/healing/run-all", { execution_mode: executionMode }), "六项自动验证已启动"); }); $("#repairReset").addEventListener("click", async () => { if (executionMode === "bench" && !(await confirmAction("四轴将依次回到机械零位。请确认运动区域无人、无遮挡且急停可用。", "确认归零"))) return; runAction(() => apiPost("/healing/reset", { execution_mode: executionMode }), executionMode === "bench" ? "四轴归零请求已提交" : "自修复验证已重置"); }); $("#repairFirmwareFlash").addEventListener("click", async () => { if (!(await confirmAction("即将构建并烧录台架测试固件。请确认座椅运动区域无人、急停可用且 CMSIS-DAP 已连接。", "确认构建/烧录"))) return; runAction(() => apiPost("/firmware/build-flash", { confirmed_bench_clear: true }), "固件构建/烧录流程已完成"); }); $("#repairHardwareToggle").addEventListener("click", async () => { if (!latest) return; if (latest.hardware_session?.active) { runAction(() => apiPost("/hardware/stop"), "硬件诊断已停止并保存报告"); return; } if (!(await confirmAction("硬件诊断可能自动烧录测试固件,并短时驱动四路电机、风扇与低档加热。请确认台架区域安全且急停可用。", "确认启动诊断"))) return; runAction(() => apiPost("/hardware/start", { confirmed_bench_clear: true }), "21 针主动硬件诊断已启动"); }); $("#repairHardwareSelfTest").addEventListener("click", () => runAction(() => apiPost("/hardware/self-test"), "72 针映射和诊断规则自检完成")); $("#repairEmergencyStop").addEventListener("click", async () => { if (!(await confirmAction("确认提交 STOP ALL 和热管理关闭请求?", "确认紧急停止"))) return; runAction(() => apiPost("/emergency-stop"), "紧急停止请求已提交"); }); $("#repairConfirmCancel").addEventListener("click", () => finishConfirmation(false)); $("#repairConfirmAccept").addEventListener("click", () => finishConfirmation(true)); $("#repairConfirmDialog").addEventListener("cancel", (event) => { event.preventDefault(); finishConfirmation(false); }); $("#repairConfirmDialog").addEventListener("close", () => { if (confirmationResolve) finishConfirmation(false); }); $("#repairSoftwareRecords").addEventListener("click", () => $("#repairSoftwareDialog").showModal()); $("#repairHardwareRecords").addEventListener("click", () => $("#repairHardwareDialog").showModal()); $$('[data-repair-close]').forEach((button) => button.addEventListener("click", () => $(`#${button.dataset.repairClose}`).close())); window.addEventListener("cognitive-agent:module-change", (event) => { if (event.detail?.module === "self-repair") poll(); }); window.addEventListener("cognitive-agent:demo-role-change", () => { if (isActive()) poll(); }); } function init() { if (!buildShell()) return; bindEvents(); showView(activeView); window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } }); if (isActive()) poll(); window.setInterval(poll, 600); } document.addEventListener("DOMContentLoaded", init); }());