feat: refine vehicle console and self-repair workflow
This commit is contained in:
+246
-88
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user