feat: refine vehicle console and self-repair workflow

This commit is contained in:
vee
2026-08-13 17:38:04 +08:00
parent fd3c219c39
commit 0ec43052f3
20 changed files with 3925 additions and 552 deletions
+246 -88
View File
@@ -4,16 +4,6 @@
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: "告警",
@@ -23,13 +13,67 @@
NOT_USED: "未启用",
IDLE: "检测中",
};
const TIMELINE = [
{ title: "开始正常任务", phases: ["BASELINE"] },
{ title: "故障发生并停止", phases: ["INJECTED"] },
{ title: "AI判断原因", phases: ["DIAGNOSED"] },
{ title: "执行恢复策略", phases: ["REPAIRING"] },
{ title: "继续原任务", phases: ["VERIFYING", "PASSED"] },
];
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)];
@@ -39,7 +83,7 @@
.replaceAll(">", ">")
.replaceAll('"', """);
let latest = null;
let latest = fallbackState();
let selectedCaseId = null;
let selectedPinId = "JC1-1";
let activeView = sessionStorage.getItem(VIEW_KEY) === "hardware"
@@ -51,6 +95,7 @@
let polling = false;
let toastTimer = 0;
let confirmationResolve = null;
let eventFilter = "all";
function isAdmin() {
return window.CognitiveAgentConsoleV2?.getRole() === "admin"
@@ -118,7 +163,7 @@
root.setAttribute("aria-label", "座椅 AI 自修复验证台");
root.innerHTML = `
<header class="v2-repair-heading">
<div><span class="section-kicker">COGNITIVE SEAT / SELF-REPAIR</span><h1>座椅AI自修复验证台</h1><p>量产软件故障闭环恢复 · 开发板接口在线诊断</p></div>
<div class="v2-module-heading"><span class="section-kicker">SELF-REPAIR</span><h1>座椅 AI 自修复验证</h1><p>座椅持续自由运行 · 故障独立注入 · 全过程事件留痕</p></div>
<div class="v2-repair-status-grid">
<div><i id="repairCanDot"></i><span><small>CAN桥接</small><strong id="repairCanStatus">离线</strong></span></div>
<div><i id="repairProtocolDot"></i><span><small>座椅ECU</small><strong id="repairProtocolStatus">未检测</strong></span></div>
@@ -128,22 +173,15 @@
<section class="v2-repair-view" data-repair-page="software">
<header class="v2-repair-toolbar">
<div><span>01</span><div><h2>软件自修复</h2><p>正常任务 → 故障停止 → AI 判断 → 安全修复 → 继续原目标</p></div></div>
<div><span>01</span><div><h2>连续运行与自修复验证</h2><p>运行任务与故障注入相互独立;选择用例不会启动或重置座椅</p></div></div>
<div>
<button type="button" id="repairSoftwareRecords"><i data-lucide="history"></i>验证记录</button>
<button type="button" id="repairFirmwareFlash"><i data-lucide="microchip"></i>构建/烧录</button>
<button type="button" id="repairReset"><i data-lucide="rotate-ccw"></i>重置归零</button>
<button type="button" id="repairRunAll"><i data-lucide="list-checks"></i>批量验证6项</button>
<button class="primary" type="button" id="repairStart"><i data-lucide="play"></i>启动当前用例</button>
<button type="button" id="repairReset"><i data-lucide="rotate-ccw"></i>重置统计</button>
<button class="primary" type="button" id="repairRuntimeToggle"><i data-lucide="play"></i>启动座椅运行</button>
</div>
</header>
<div class="v2-repair-software-grid">
<section class="v2-repair-panel v2-repair-cases-panel">
<header><div><span class="section-kicker">PRODUCTION CASES</span><h2>量产故障用例</h2></div><b id="repairBatchCount">0 / 6</b></header>
<div class="v2-repair-overall-progress" aria-label="六项用例总体进度"><span id="repairBatchProgress"></span></div>
<p id="repairBatchSummary">等待启动批量验证</p>
<div class="v2-repair-case-list" id="repairCaseList"></div>
</section>
<section class="v2-repair-panel v2-repair-twin-panel">
<header><div><span class="section-kicker">DIGITAL TWIN</span><h2>座椅3D孪生</h2></div><b id="repairTwinState">正常基线</b></header>
<div class="v2-repair-seat-viewer" id="repairSeatViewer">
@@ -153,9 +191,31 @@
<div class="v2-repair-twin-live"><div><small id="repairTwinSource">影子动作仿真</small><strong id="repairTwinTask">等待启动座椅任务</strong><b id="repairTwinStage">待机</b></div><div id="repairAxisStrip"></div><p id="repairTwinReaction">实际动作将同步显示在座椅模型</p></div>
</div>
</section>
<section class="v2-repair-panel v2-repair-timeline-panel">
<header><div><span class="section-kicker">HEALING FLOW</span><h2>座椅任务自修复</h2></div><b id="repairElapsed">0.000 s</b></header>
<div class="v2-repair-timeline" id="repairTimeline"></div>
<section class="v2-repair-panel v2-repair-cases-panel">
<header><div><span class="section-kicker">FAULT INJECTION</span><h2>量产故障注入</h2></div><b id="repairCaseCount">6 个用例</b></header>
<p class="v2-repair-injection-note">先启动座椅持续运行,再按需选择故障。注入事件会明确标记为管理员操作。</p>
<div class="v2-repair-case-list" id="repairCaseList"></div>
<footer class="v2-repair-injection-footer">
<small id="repairSelectedCase">当前选择:—</small>
<button class="primary" type="button" id="repairInject"><i data-lucide="syringe"></i>注入所选故障</button>
<span id="repairInjectionCount">本次尚未注入故障</span>
</footer>
</section>
<section class="v2-repair-panel v2-repair-log-panel">
<header><div><span class="section-kicker">EVENT STREAM</span><h2>运行与自修复日志</h2></div><b id="repairLogState" data-state="idle">等待启动</b></header>
<div class="v2-repair-log-summary">
<div><small>本次连续运行</small><strong id="repairRuntimeElapsed">00:00:00</strong></div>
<div><small>累计健康运行</small><strong id="repairHealthyElapsed">00:00:00</strong></div>
<div><small>故障 / 已修复</small><strong id="repairFaultSummary">0 / 0</strong></div>
</div>
<div class="v2-repair-log-toolbar">
<div role="group" aria-label="日志筛选">
<button class="active" type="button" data-repair-log-filter="all">全部事件</button>
<button type="button" data-repair-log-filter="fault">仅故障与修复</button>
</div>
<span><i></i>自动跟随最新日志</span>
</div>
<div class="v2-repair-event-stream" id="repairEventStream" aria-live="polite"></div>
</section>
</div>
</section>
@@ -240,36 +300,92 @@
|| 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 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 renderTimeline() {
const healing = latest.healing;
const scenario = scenarioForDisplay();
if (!scenario) return;
const phaseIndex = PHASE_INDEX[healing.phase] ?? -1;
const passed = healing.phase === "PASSED";
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) => `<article data-kind="${esc(event.kind)}"><time>${esc(event.time || "--:--:--.---")}</time><i></i><div><header><span>${esc(event.label)}</span><strong>${esc(event.title)}</strong></header><p>${esc(event.detail || "状态已更新")}</p>${event.tags?.length ? `<footer>${event.tags.map((tag) => `<span>${esc(tag)}</span>`).join("")}</footer>` : ""}</div></article>`).join("")
: `<div class="v2-repair-log-empty"><i data-lucide="scroll-text"></i><strong>${runtime.active ? "座椅正在正常运行" : "等待启动座椅运行"}</strong><span>${runtime.active ? "运行心跳、故障、诊断和修复事件将在这里持续追加。" : "启动后将记录运行时长、四轴状态和后续故障注入。"}</span></div>`;
$("#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";
$("#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 `<article data-state="${state}"><i></i><div><header><strong>${esc(stage.title)}</strong><span>${label}</span></header><p>${esc(timelineDetail(index, scenario, healing))}</p></div></article>`;
}).join("");
$("#repairElapsed").textContent = `${Number(healing.elapsed_s || 0).toFixed(3)} s`;
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() {
@@ -290,26 +406,27 @@
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}`
: "等待启动批量验证";
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 = `<i data-lucide="syringe"></i>${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";
@@ -337,11 +454,19 @@
? (live.online ? "实物 CAN 同步" : "实物 CAN 暂离线")
: "影子动作仿真";
$("#repairTwinTask").textContent = healing.phase === "IDLE"
? `等待启动:${scenario.normal_task}` : scenario.normal_task;
$("#repairTwinStage").textContent = stage || "待机";
$("#repairTwinState").textContent = healing.phase_text;
? (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" ? scenario.field_basis : scenario.fault_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 `<span class="${axis.active ? "active" : ""}"><b>M${axis.motor}</b><i style="--position:${position}%"></i><em>${position.toFixed(0)}%</em></span>`;
@@ -441,6 +566,7 @@
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 ? "协议在线" : "未检测";
@@ -453,9 +579,13 @@
}[hardwarePhase];
const aiState = activeView === "hardware" && hardwareState
? hardwareState
: [healing.phase_text || "等待验证", healing.phase === "PASSED"
? "online"
: healing.phase === "IDLE" ? "offline" : "working"];
: 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 桥接离线";
@@ -472,6 +602,14 @@
? "真实台架动作受本机 admin、安全确认与桥接状态约束"
: "影子仿真不会写入 ECU 或驱动实体设备";
$("#repairFirmwareFlash").disabled = !integration.firmware_tools_configured;
const runtimeToggle = $("#repairRuntimeToggle");
const repairActive = ["INJECTED", "DIAGNOSED", "REPAIRING", "VERIFYING"].includes(healing.phase);
runtimeToggle.innerHTML = runtime.active
? '<i data-lucide="square"></i>停止座椅运行'
: '<i data-lucide="play"></i>启动座椅运行';
runtimeToggle.classList.toggle("danger", runtime.active);
runtimeToggle.classList.toggle("primary", !runtime.active);
runtimeToggle.disabled = repairActive;
window.CognitiveAgentConsoleV2?.enhanceSelect($("#repairExecutionMode"));
}
@@ -484,7 +622,7 @@
function renderAll() {
renderGlobal();
renderCases();
renderTimeline();
renderEventStream();
renderTwin();
renderHardware();
renderRecords();
@@ -550,20 +688,33 @@
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 () => {
$("#repairRuntimeToggle").addEventListener("click", async () => {
const active = Boolean(runtimeState().active);
if (executionMode === "bench" && !(await confirmAction(
"将依次执行6个实物故障用例;每项开始前会自动停止输出并完成四轴归零。请确认运动区域无人、无遮挡且急停可用。",
"确认实物批量验证",
active
? "即将停止实物座椅连续运行。请确认可安全停止。"
: "即将启动实物座椅四电机协同自由运动。请确认运动区域无人、无遮挡且急停可用。",
active ? "确认停止" : "确认启动",
))) return;
runAction(() => apiPost("/healing/run-all", { execution_mode: executionMode }), "六项自动验证已启动");
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 () => {
if (executionMode === "bench" && !(await confirmAction("四轴将依次回到机械零位。请确认运动区域无人、无遮挡且急停可用。", "确认归零"))) return;
runAction(() => apiPost("/healing/reset", { execution_mode: executionMode }), executionMode === "bench" ? "四轴归零请求已提交" : "自修复验证已重置");
runAction(() => apiPost("/runtime/reset-statistics"), "本次运行统计已重置");
});
$("#repairFirmwareFlash").addEventListener("click", async () => {
if (!(await confirmAction("即将构建并烧录台架测试固件。请确认座椅运动区域无人、急停可用且 CMSIS-DAP 已连接。", "确认构建/烧录"))) return;
@@ -594,6 +745,12 @@
});
$("#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();
@@ -607,6 +764,7 @@
if (!buildShell()) return;
bindEvents();
showView(activeView);
renderAll();
window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } });
if (isActive()) poll();
window.setInterval(poll, 600);