优化无线/有线CAN,以及控制指令解析
This commit is contained in:
+117
-25
@@ -22,6 +22,31 @@
|
||||
|
||||
let reasoningPickerControl = null;
|
||||
let validatedCommandsVisible = false;
|
||||
let validatedCommandContext = { requestId: "", utterance: "" };
|
||||
|
||||
function resetValidatedCommandContext() {
|
||||
validatedCommandsVisible = false;
|
||||
validatedCommandContext = { requestId: "", utterance: "" };
|
||||
}
|
||||
|
||||
function rememberValidatedCommandContext(result = {}) {
|
||||
validatedCommandContext = {
|
||||
requestId: String(result.request_id || ""),
|
||||
utterance: String(result.utterance || "").trim(),
|
||||
};
|
||||
}
|
||||
|
||||
function validatedCommandsBelongToTask(task = {}) {
|
||||
if (!validatedCommandsVisible) return false;
|
||||
const result = task.result && typeof task.result === "object" ? task.result : {};
|
||||
const requestId = String(result.request_id || "");
|
||||
const utterance = String(task.utterance || result.utterance || "").trim();
|
||||
if (validatedCommandContext.requestId && requestId
|
||||
&& validatedCommandContext.requestId !== requestId) return false;
|
||||
if (validatedCommandContext.utterance && utterance
|
||||
&& validatedCommandContext.utterance !== utterance) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function make(tag, className, html) {
|
||||
const node = document.createElement(tag);
|
||||
@@ -71,10 +96,13 @@
|
||||
...(parseSource ? { parseSource } : {}),
|
||||
});
|
||||
const hexByte = (value) => Number(value).toString(16).padStart(2, "0").toUpperCase();
|
||||
const otaCan0Frame = (opcode, selector, argument) => {
|
||||
const numeric = Number(argument) >>> 0;
|
||||
const argumentBytes = [0, 8, 16, 24].map((shift) => hexByte((numeric >>> shift) & 0xFF));
|
||||
return `0x6A0 · C7 <SEQ> ${hexByte(opcode)} ${hexByte(selector)} ${argumentBytes.join(" ")}`;
|
||||
const littleEndian16 = (value) => {
|
||||
const numeric = Number(value) & 0xFFFF;
|
||||
return `${hexByte(numeric & 0xFF)} ${hexByte((numeric >>> 8) & 0xFF)}`;
|
||||
};
|
||||
const nativeCan0Frame = (opcode, motor, direction, paramA, paramB) => {
|
||||
const control = (Number(motor) & 0x03) | ((Number(direction) & 0x03) << 2) | 0x10;
|
||||
return `0x300 · ${hexByte(opcode)} ${hexByte(control)} <SEQ_LO> <SEQ_HI> ${littleEndian16(paramA)} ${littleEndian16(paramB)}`;
|
||||
};
|
||||
|
||||
const isPassengerSeat = new Set(["passenger", "front-right"]).has(target);
|
||||
@@ -88,7 +116,6 @@
|
||||
const axis = passengerAxes[capability];
|
||||
const position = boundedInteger(parameters.position_pct, 0, 100);
|
||||
if (axis && position !== null) {
|
||||
const targetPermille = position * 10;
|
||||
const motion = operation.motion || {};
|
||||
const delta = boundedInteger(motion.delta_position_pct, 1, 100);
|
||||
const relativeDirections = {
|
||||
@@ -104,7 +131,9 @@
|
||||
? `seat passenger ${axis[1]} relative ${relativeDirection} ${delta}`
|
||||
: `seat passenger ${axis[1]} position ${position}`,
|
||||
"CAN0",
|
||||
otaCan0Frame(0x64, axis[2], targetPermille),
|
||||
position === 0
|
||||
? nativeCan0Frame(0x0B, axis[2], 0, 0x4B52, 120)
|
||||
: nativeCan0Frame(0x01, axis[2], 0, position * 100, 30000),
|
||||
);
|
||||
}
|
||||
if (new Set(["seat.set_ventilation", "seat.set_heating"]).has(capability)) {
|
||||
@@ -124,14 +153,12 @@
|
||||
if (capability === "seat.start_dynamic_mode") {
|
||||
const mode = boundedInteger(parameters.mode_id, 1, 2);
|
||||
if (mode !== null) {
|
||||
const dynamic = mode === 1
|
||||
? { name: "rocking-horse", selector: 2 }
|
||||
: { name: "cradle", selector: 1 };
|
||||
const dynamic = mode === 1 ? "rocking-horse" : "cradle";
|
||||
return command(
|
||||
"副驾座椅动态模式",
|
||||
`seat passenger dynamic ${dynamic.name}`,
|
||||
`seat passenger dynamic ${dynamic}`,
|
||||
"CAN0",
|
||||
otaCan0Frame(0x66, dynamic.selector, 300),
|
||||
nativeCan0Frame(0x0E, 0, 0, mode, 0),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -140,7 +167,7 @@
|
||||
"副驾座椅动态模式",
|
||||
"seat passenger dynamic stop",
|
||||
"CAN0",
|
||||
otaCan0Frame(0x63, 0xFF, 0),
|
||||
nativeCan0Frame(0x0F, 0, 0, 0, 0),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -198,6 +225,13 @@
|
||||
const mode = String(parameters.mode ?? parameters.cycle_mode ?? "");
|
||||
if (new Set(["internal", "external"]).has(mode)) return command("空调循环", `ac cycle ${mode}`);
|
||||
}
|
||||
if (capability === "outlet.set_switch_state") {
|
||||
const state = String(parameters.state ?? "");
|
||||
if (new Set(["driver-left", "driver-right", "passenger-left", "passenger-right"]).has(target)
|
||||
&& new Set(["open", "close"]).has(state)) {
|
||||
return command("单风口开关", `outlet ${target} ${state}`);
|
||||
}
|
||||
}
|
||||
if (capability === "outlet.set_position_percent") {
|
||||
const horizontal = boundedInteger(parameters.horizontal_pct, 0, 100);
|
||||
const vertical = boundedInteger(parameters.vertical_pct, 0, 100);
|
||||
@@ -330,7 +364,7 @@
|
||||
${escapeHtml(item.category)}
|
||||
</span>
|
||||
<code>${escapeHtml(item.command)}</code>
|
||||
${item.frame ? `<small class="v2-can-command-frame">seat-ota · ${escapeHtml(item.frame)}</small>` : ""}
|
||||
${item.frame ? `<small class="v2-can-command-frame">固件原生 CAN0 · ${escapeHtml(item.frame)}</small>` : ""}
|
||||
</li>`).join("")}</ol>`;
|
||||
} else {
|
||||
list.innerHTML = `<p>${escapeHtml(message || "本次请求未生成已验证的 CAN 控制指令")}</p>`;
|
||||
@@ -359,6 +393,10 @@
|
||||
<code>${escapeHtml(item.command)}</code>
|
||||
</li>`).join("")}</ol>`;
|
||||
validatedCommandsVisible = true;
|
||||
validatedCommandContext = {
|
||||
requestId: "",
|
||||
utterance: items.map((item) => item.command).join(";"),
|
||||
};
|
||||
requestAnimationFrame(syncSidebarLogBaseline);
|
||||
}
|
||||
|
||||
@@ -1184,6 +1222,12 @@
|
||||
if ($("#v2TaskKind")) $("#v2TaskKind").textContent = kind;
|
||||
if ($("#v2TaskCommand")) $("#v2TaskCommand").textContent = utterance || "等待车机端或网页端选择预设模式、输入一句话指令";
|
||||
const normalizedStatus = String(task.status || "idle").toLowerCase();
|
||||
const verificationStatus = String(
|
||||
result?.execution_outcome
|
||||
|| result?.execution_report?.spec?.verification_status
|
||||
|| "",
|
||||
);
|
||||
const submittedUnverified = verificationStatus === "submitted_unverified";
|
||||
const statusMap = {
|
||||
idle: ["等待任务", "idle"],
|
||||
running: ["构建 / 执行中", "running"],
|
||||
@@ -1192,7 +1236,9 @@
|
||||
needs_confirmation: ["等待确认", "partial"],
|
||||
failed: ["执行失败", "failed"],
|
||||
};
|
||||
const [label, state] = statusMap[normalizedStatus] || statusMap.idle;
|
||||
const [label, state] = submittedUnverified && normalizedStatus === "succeeded"
|
||||
? ["指令已提交 · 等待状态确认", "partial"]
|
||||
: statusMap[normalizedStatus] || statusMap.idle;
|
||||
const badge = $("#v2TaskState");
|
||||
if (badge) {
|
||||
badge.textContent = label;
|
||||
@@ -1207,9 +1253,23 @@
|
||||
});
|
||||
renderTaskTimeline(task);
|
||||
if (normalizedStatus === "running") {
|
||||
renderParsedCanCommands(null, { message: "正在解析自然语言并生成 CAN 控制指令…" });
|
||||
// The streaming endpoint can publish fully validated commands before the
|
||||
// slower display-sync poll observes the terminal execution state. A stale
|
||||
// `running` snapshot for the same task must not erase those commands.
|
||||
if (!validatedCommandsBelongToTask(task)) {
|
||||
resetValidatedCommandContext();
|
||||
renderParsedCanCommands(null, { message: "正在解析自然语言并生成 CAN 控制指令…" });
|
||||
}
|
||||
} else if (result) {
|
||||
renderParsedCanCommands(result);
|
||||
const commands = parsedCanCommands(result);
|
||||
if (commands.length) {
|
||||
validatedCommandsVisible = true;
|
||||
rememberValidatedCommandContext(result);
|
||||
renderParsedCanCommands(result);
|
||||
} else if (!validatedCommandsBelongToTask(task)) {
|
||||
resetValidatedCommandContext();
|
||||
renderParsedCanCommands(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1245,12 +1305,14 @@
|
||||
setTaskPresentation(task);
|
||||
const sourceLabel = task.source === "web_console" ? "网页端" : "车机端";
|
||||
if (task.utterance) {
|
||||
const submittedUnverified = task.result?.execution_outcome === "submitted_unverified"
|
||||
|| task.result?.execution_report?.spec?.verification_status === "submitted_unverified";
|
||||
appendSyncLog({
|
||||
key: `task:${revision}:${task.status}`,
|
||||
category: "task",
|
||||
title: `${sourceLabel}${task.status === "running" ? "下发编排任务" : "任务状态已同步"}`,
|
||||
detail: `${task.utterance} · ${task.status || "idle"}`,
|
||||
status: ["succeeded", "partially_succeeded"].includes(task.status) ? "success" : task.status === "failed" ? "error" : "info",
|
||||
detail: `${task.utterance} · ${submittedUnverified ? "指令已提交,等待车辆状态确认" : task.status || "idle"}`,
|
||||
status: submittedUnverified ? "info" : ["succeeded", "partially_succeeded"].includes(task.status) ? "success" : task.status === "failed" ? "error" : "info",
|
||||
});
|
||||
}
|
||||
const result = task.result;
|
||||
@@ -1487,26 +1549,44 @@
|
||||
});
|
||||
$("#v2LogFilter")?.addEventListener("change", renderSyncLogs);
|
||||
$("#requestForm")?.addEventListener("submit", () => {
|
||||
validatedCommandsVisible = false;
|
||||
resetValidatedCommandContext();
|
||||
renderParsedCanCommands(null, { message: "正在解析自然语言并生成 CAN 控制指令…" });
|
||||
});
|
||||
window.addEventListener("cognitive-agent:validated-can-commands", (event) => {
|
||||
const result = event.detail || {};
|
||||
validatedCommandsVisible = parsedCanCommands(result).length > 0;
|
||||
if (validatedCommandsVisible) rememberValidatedCommandContext(result);
|
||||
else resetValidatedCommandContext();
|
||||
renderParsedCanCommands(result, {
|
||||
message: "已完成白名单校验,正在下发控制指令…",
|
||||
});
|
||||
});
|
||||
window.addEventListener("cognitive-agent:result", (event) => {
|
||||
const result = event.detail || {};
|
||||
validatedCommandsVisible = parsedCanCommands(result).length > 0;
|
||||
renderParsedCanCommands(result);
|
||||
const submittedUnverified = result.execution_outcome === "submitted_unverified"
|
||||
|| result.execution_report?.spec?.verification_status === "submitted_unverified";
|
||||
const commands = parsedCanCommands(result);
|
||||
if (commands.length) {
|
||||
validatedCommandsVisible = true;
|
||||
rememberValidatedCommandContext(result);
|
||||
renderParsedCanCommands(result);
|
||||
} else if (!validatedCommandsBelongToTask({
|
||||
utterance: result.utterance,
|
||||
result,
|
||||
})) {
|
||||
resetValidatedCommandContext();
|
||||
renderParsedCanCommands(result);
|
||||
}
|
||||
appendSyncLog({
|
||||
key: result.request_id ? `result:${result.request_id}` : "",
|
||||
category: "task",
|
||||
title: ["succeeded", "partially_succeeded"].includes(result.status) ? "编排任务执行完成" : "编排任务返回结果",
|
||||
detail: `${result.summary || result.message || "执行反馈已同步"} · ${result.status || "unknown"}`,
|
||||
status: ["succeeded", "partially_succeeded"].includes(result.status) ? "success" : result.status === "failed" ? "error" : "info",
|
||||
title: submittedUnverified
|
||||
? "CAN 指令已提交,等待车辆状态确认"
|
||||
: ["succeeded", "partially_succeeded"].includes(result.status)
|
||||
? "编排任务执行完成"
|
||||
: "编排任务返回结果",
|
||||
detail: `${result.summary || result.message || "执行反馈已同步"} · ${submittedUnverified ? "submitted_unverified" : result.status || "unknown"}`,
|
||||
status: submittedUnverified ? "info" : ["succeeded", "partially_succeeded"].includes(result.status) ? "success" : result.status === "failed" ? "error" : "info",
|
||||
});
|
||||
});
|
||||
window.addEventListener("cognitive-agent:request-complete", (event) => {
|
||||
@@ -1514,7 +1594,19 @@
|
||||
renderParsedCanCommands(null, { message: "本次请求未生成 CAN 控制指令" });
|
||||
}
|
||||
if (event.detail?.error) {
|
||||
appendSyncLog({ category: "task", title: "编排任务执行失败", detail: event.detail.error.message || "请求失败", status: "error" });
|
||||
const error = event.detail.error;
|
||||
const outcomeLabels = {
|
||||
failed_or_blocked: "失败或已阻止",
|
||||
submitted_unverified: "已提交但未验证",
|
||||
verified_success: "反馈验证成功",
|
||||
};
|
||||
const outcome = outcomeLabels[error.outcome];
|
||||
appendSyncLog({
|
||||
category: "task",
|
||||
title: "编排任务执行失败",
|
||||
detail: `${error.detail || error.message || "请求失败"}${outcome ? ` · ${outcome}` : ""}`,
|
||||
status: "error",
|
||||
});
|
||||
}
|
||||
});
|
||||
window.addEventListener("cognitive-agent:connection", (event) => {
|
||||
|
||||
Reference in New Issue
Block a user