(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 STATE_TEXT = { OK: "正常", WARN: "告警", FAULT: "故障", UNKNOWN: "未检测", UNMONITORED: "关联待测", NOT_USED: "未启用", IDLE: "检测中", }; const EVENT_META = { BASELINE: { kind: "system", label: "运行" }, INJECTED: { kind: "fault", label: "注入" }, DIAGNOSED: { kind: "diagnosis", label: "诊断" }, REPAIRING: { kind: "repair", label: "修复" }, VERIFYING: { kind: "repair", label: "复测" }, PASSED: { kind: "success", label: "恢复" }, FAILED: { kind: "fault", label: "失败" }, }; const FALLBACK_SCENARIOS = [ ["FI-FIELD-01", "位置标定丢失", "座椅移动 / 位置记忆"], ["FI-FIELD-02", "位置记录CRC回退", "四轴位置 / SMV8持久化"], ["FI-FIELD-03", "瞬态过载误判", "电机移动 / 防夹保护"], ["FI-FIELD-04", "热管理唤醒失败", "通风 / 加热"], ["FI-FIELD-05", "风扇关闭异常", "座椅通风"], ["FI-FIELD-06", "WDT复位后失能", "座椅控制器 / 多功能协同"], ].map(([id, shortTitle, system]) => ({ id, short_title: shortTitle, system, normal_task: "等待连接后加载正常任务", fault_reaction: "等待连接后加载故障停止状态", diagnosis: "等待连接后加载 AI 诊断", repair: "等待连接后加载恢复策略", recovery_reaction: "等待连接后加载恢复结果", field_basis: "自修复服务连接后显示实时用例说明", })); function fallbackState(message = "正在连接自修复服务") { return { scenarios: FALLBACK_SCENARIOS, healing: { phase: "IDLE", phase_text: "等待服务连接", elapsed_s: 0, case_id: null, telemetry: {}, events: [], }, batch: { active: false, completed: 0, total: 6, passed: 0, failed: 0, results: [] }, runtime: { active: false, execution_mode: "shadow", started_epoch: null, stopped_epoch: null, elapsed_s: 0, healthy_elapsed_s: 0, motion_profile: "四电机协同自由运动", cycle_count: 0, injected_count: 0, repaired_count: 0, events: [], }, bridge: { online: false, protocol_online: false, bus_traffic: false }, bridge_manager: { state: "STARTING", message }, integration: { can_runtime_configured: false, firmware_tools_configured: false }, hardware: { groups: [], connectors: {} }, hardware_session: { active: false, events: [] }, bench_twin: {}, }; } 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 = fallbackState(); let selectedCaseId = null; let selectedPinId = "JC1-1"; let activeView = sessionStorage.getItem(VIEW_KEY) === "hardware" ? "hardware" : "software"; sessionStorage.removeItem(MODE_KEY); let executionMode = "shadow"; let polling = false; let bootstrapRuntimeReset = false; let toastTimer = 0; let confirmationResolve = null; let eventFilter = "all"; 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 = `
SELF-REPAIR

座椅 AI 自修复验证

座椅持续自由运行 · 故障独立注入 · 全过程事件留痕

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

连续运行与自修复验证

运行任务与故障注入相互独立;选择用例不会启动或重置座椅

DIGITAL TWIN

座椅3D孪生

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

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

FAULT INJECTION

量产故障注入

6 个用例

先启动座椅持续运行,再按需选择故障。注入事件会明确标记为管理员操作。

当前选择:— 本次尚未注入故障
EVENT STREAM

运行与自修复日志

等待启动
本次连续运行00:00:00
累计健康运行00:00:00
故障 / 已修复0 / 0
自动跟随最新日志
软件自修复验证记录成功用例自动保存诊断快照
硬件自诊断记录支路状态变化与最终报告
确认台架安全条件确认后才会向实物链路提交请求

`; 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 formatDuration(seconds) { const whole = Math.max(0, Math.floor(Number(seconds || 0))); const hours = Math.floor(whole / 3600); const minutes = Math.floor((whole % 3600) / 60); const remainder = whole % 60; return [hours, minutes, remainder] .map((value) => String(value).padStart(2, "0")) .join(":"); } function formatEventTime(epoch) { if (!epoch) return "--:--:--.---"; const value = new Date(Number(epoch) * 1000); const base = [value.getHours(), value.getMinutes(), value.getSeconds()] .map((item) => String(item).padStart(2, "0")) .join(":"); return `${base}.${String(value.getMilliseconds()).padStart(3, "0")}`; } function runtimeState() { return latest.runtime || fallbackState().runtime; } function renderEventStream() { const runtime = runtimeState(); const healing = latest.healing || {}; const scenario = (latest.scenarios || []).find((item) => item.id === healing.case_id) || scenarioForDisplay(); const runtimeEvents = (runtime.events || []).map((event) => ({ ...event, kind: event.kind || "system", label: event.label || "运行", })); const healingEvents = (healing.events || []).map((event) => { const meta = EVENT_META[event.phase] || EVENT_META.BASELINE; const injected = event.phase === "INJECTED"; return { ...event, kind: meta.kind, label: meta.label, title: injected ? `管理员注入「${scenario?.short_title || "故障用例"}」` : event.title, tags: [ injected ? "来源:admin" : null, healing.run_id ? `事件:${healing.run_id}` : null, healing.diagnosed_code && ["DIAGNOSED", "REPAIRING", "VERIFYING", "PASSED", "FAILED"].includes(event.phase) ? `诊断码:${healing.diagnosed_code}` : null, ].filter(Boolean), }; }); const activeRepair = ["INJECTED", "DIAGNOSED", "REPAIRING", "VERIFYING"].includes(healing.phase); const twin = latest.bench_twin || {}; const naturalFault = runtime.active && !activeRepair && ["IDLE", "PASSED"].includes(healing.phase) && Number(twin.system?.faulted_mask || 0) !== 0; const liveEvents = runtime.active && !activeRepair ? [{ epoch: latest.generated_epoch, time: formatEventTime(latest.generated_epoch), kind: naturalFault ? "fault" : "heartbeat", label: naturalFault ? "自然故障" : "运行心跳", title: naturalFault ? "运行中检测到座椅异常" : `座椅已正常运行 ${formatDuration(runtime.healthy_elapsed_s)}`, detail: naturalFault ? `ECU FaultedMask=0x${Number(twin.system.faulted_mask).toString(16).toUpperCase()},AI正在持续监测。` : `${runtime.motion_profile} · 已完成 ${runtime.cycle_count || 0} 个动作周期。`, tags: (twin.axes?.length ? twin.axes : [35, 42, 48, 20].map((position_pct, motor) => ({ motor, position_pct }))) .map((axis) => `M${axis.motor} ${Number(axis.position_pct || 0).toFixed(0)}%`), }] : []; const events = [...runtimeEvents, ...healingEvents, ...liveEvents] .filter((event) => eventFilter === "all" || ["fault", "diagnosis", "repair", "success"].includes(event.kind)) .sort((left, right) => Number(left.epoch || 0) - Number(right.epoch || 0)); $("#repairEventStream").innerHTML = events.length ? events.map((event) => `
${esc(event.label)}${esc(event.title)}

${esc(event.detail || "状态已更新")}

${event.tags?.length ? `` : ""}
`).join("") : `
${runtime.active ? "座椅正在正常运行" : "等待启动座椅运行"}${runtime.active ? "运行心跳、故障、诊断和修复事件将在这里持续追加。" : "启动后将记录运行时长、四轴状态和后续故障注入。"}
`; $("#repairEventStream").scrollTop = $("#repairEventStream").scrollHeight; $("#repairRuntimeElapsed").textContent = formatDuration(runtime.elapsed_s); $("#repairHealthyElapsed").textContent = formatDuration(runtime.healthy_elapsed_s); $("#repairFaultSummary").textContent = `${Number(runtime.injected_count || 0)} / ${Number(runtime.repaired_count || 0)}`; const failed = healing.phase === "FAILED"; const repairing = activeRepair; const logState = $("#repairLogState"); logState.textContent = failed ? "需要处理" : repairing ? "自修复中" : healing.phase === "PASSED" && runtime.active ? "已恢复运行" : runtime.active ? "健康运行" : "等待启动"; logState.dataset.state = failed ? "fault" : repairing ? "repairing" : runtime.active ? "healthy" : "idle"; } 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(); renderTwin(); }); }); const selected = scenarioForDisplay(); const runtime = runtimeState(); $("#repairCaseCount").textContent = `${(latest.scenarios || []).length} 个用例`; $("#repairSelectedCase").textContent = selected ? `当前选择:${selected.short_title}` : "当前选择:—"; $("#repairInjectionCount").textContent = Number(runtime.injected_count || 0) ? `本次已注入 ${runtime.injected_count} 次 · 已修复 ${runtime.repaired_count || 0} 次` : "本次尚未注入故障"; const activeRepair = !["IDLE", "PASSED", "FAILED"].includes(latest.healing?.phase); $("#repairInject").disabled = !runtime.active || activeRepair || latest.batch?.active; $("#repairInject").innerHTML = `${activeRepair ? "自修复处理中" : runtime.active ? "注入所选故障" : "请先启动座椅运行"}`; } function renderTwin() { const healing = latest.healing; const scenario = scenarioForDisplay(); if (!scenario) return; const runtime = runtimeState(); 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" ? (runtime.active ? runtime.motion_profile : "等待启动座椅运行") : scenario.normal_task; $("#repairTwinStage").textContent = healing.phase === "IDLE" && runtime.active ? "自由运动" : stage || "待机"; $("#repairTwinState").textContent = healing.phase === "IDLE" ? (runtime.active ? "持续运行中" : "等待启动") : healing.phase === "PASSED" && runtime.active ? "已恢复运行" : healing.phase_text; $("#repairTwinReaction").textContent = telemetry.bench_reaction || (healing.phase === "IDLE" ? (runtime.active ? `四电机按设定轨迹协同运动 · 已完成 ${runtime.cycle_count || 0} 个周期` : "实际动作将同步显示在座椅模型") : 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", "CAN Runtime", 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 || {}; const runtime = runtimeState(); $("#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 : runtime.active && healing.phase === "IDLE" ? ["持续监控", "online"] : runtime.active && healing.phase === "PASSED" ? ["已恢复监控", "online"] : [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.can_runtime_configured; if (executionMode === "bench" && !integration.can_runtime_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; const runtimeToggle = $("#repairRuntimeToggle"); const repairActive = ["INJECTED", "DIAGNOSED", "REPAIRING", "VERIFYING"].includes(healing.phase); runtimeToggle.innerHTML = runtime.active ? '停止座椅运行' : '启动座椅运行'; runtimeToggle.classList.toggle("danger", runtime.active); runtimeToggle.classList.toggle("primary", !runtime.active); runtimeToggle.disabled = repairActive; 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(); renderEventStream(); 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 (!bootstrapRuntimeReset) { bootstrapRuntimeReset = true; const backendMode = latest.runtime?.execution_mode === "bench" ? "bench" : "shadow"; if (backendMode === "bench") { // A physical session is authoritative: retain it and render bench feedback. executionMode = "bench"; sessionStorage.setItem(MODE_KEY, executionMode); if ($("#repairExecutionMode")) $("#repairExecutionMode").value = executionMode; } else { // Shadow state is one-run-only. A full web runtime restart always returns // counters, injected faults, and the twin to the initial safe state. if (latest.runtime?.active) { await apiPost("/runtime", { active: false, execution_mode: "shadow" }); } await apiPost("/runtime/reset-statistics"); const resetResponse = await fetch(`${API_ROOT}/state`, { cache: "no-store", credentials: "same-origin", headers: { Accept: "application/json", "X-Cognitive-Agent-Role": "admin" }, }); if (resetResponse.ok) latest = await resetResponse.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(); }); $("#repairRuntimeToggle").addEventListener("click", async () => { const active = Boolean(runtimeState().active); if (executionMode === "bench" && !(await confirmAction( active ? "即将停止实物座椅连续运行。请确认可安全停止。" : "即将启动实物座椅四电机协同自由运动。请确认运动区域无人、无遮挡且急停可用。", active ? "确认停止" : "确认启动", ))) return; runAction( () => apiPost("/runtime", { active: !active, execution_mode: executionMode }), active ? "座椅连续运行已停止" : "座椅连续运行已启动", ); }); $("#repairInject").addEventListener("click", async () => { const scenario = scenarioForDisplay(); if (!runtimeState().active || !scenario) return; if (executionMode === "bench" && !(await confirmAction( `即将向实物 ECU 注入「${scenario.short_title}」并驱动座椅自修复。请确认运动区域无人、无遮挡且急停可用。`, "确认注入", ))) return; runAction( () => apiPost("/healing/start", { case_id: selectedCaseId, execution_mode: executionMode }), executionMode === "bench" ? "实物故障已注入" : "影子故障已注入,开始自修复", ); }); $("#repairReset").addEventListener("click", async () => { runAction(() => apiPost("/runtime/reset-statistics"), "本次运行统计已重置"); }); $("#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-log-filter]').forEach((button) => button.addEventListener("click", () => { eventFilter = button.dataset.repairLogFilter === "fault" ? "fault" : "all"; $$('[data-repair-log-filter]').forEach((item) => item.classList.toggle("active", item === button)); renderEventStream(); window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } }); })); $$('[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); renderAll(); window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } }); if (isActive()) poll(); window.setInterval(poll, 600); } document.addEventListener("DOMContentLoaded", init); }());