mqtt切换为远程服务器部署,优化简单指令解析时长
This commit is contained in:
+497
-41
@@ -23,15 +23,37 @@
|
||||
let reasoningPickerControl = null;
|
||||
let validatedCommandsVisible = false;
|
||||
let validatedCommandContext = { requestId: "", utterance: "" };
|
||||
let pendingSpeechTiming = null;
|
||||
let commandTimingState = {
|
||||
inputMode: "text",
|
||||
utterance: "",
|
||||
requestId: "",
|
||||
requestStartedAt: null,
|
||||
validatedAt: null,
|
||||
displayedAt: null,
|
||||
renderDurationMs: null,
|
||||
speechStartedAt: null,
|
||||
speechResultAt: null,
|
||||
speechTimings: null,
|
||||
};
|
||||
|
||||
function resetValidatedCommandContext() {
|
||||
validatedCommandsVisible = false;
|
||||
validatedCommandContext = { requestId: "", utterance: "" };
|
||||
}
|
||||
|
||||
function commandCorrelationIds(result = {}) {
|
||||
return [...new Set([
|
||||
result.preview_request_id,
|
||||
result.supplement_of,
|
||||
result.request_id,
|
||||
].map((value) => String(value || "").trim()).filter(Boolean))];
|
||||
}
|
||||
|
||||
function rememberValidatedCommandContext(result = {}) {
|
||||
const ids = commandCorrelationIds(result);
|
||||
validatedCommandContext = {
|
||||
requestId: String(result.request_id || ""),
|
||||
requestId: ids[0] || "",
|
||||
utterance: String(result.utterance || "").trim(),
|
||||
};
|
||||
}
|
||||
@@ -39,10 +61,10 @@
|
||||
function validatedCommandsBelongToTask(task = {}) {
|
||||
if (!validatedCommandsVisible) return false;
|
||||
const result = task.result && typeof task.result === "object" ? task.result : {};
|
||||
const requestId = String(result.request_id || "");
|
||||
const requestIds = commandCorrelationIds(result);
|
||||
const utterance = String(task.utterance || result.utterance || "").trim();
|
||||
if (validatedCommandContext.requestId && requestId
|
||||
&& validatedCommandContext.requestId !== requestId) return false;
|
||||
if (validatedCommandContext.requestId && requestIds.length
|
||||
&& !requestIds.includes(validatedCommandContext.requestId)) return false;
|
||||
if (validatedCommandContext.utterance && utterance
|
||||
&& validatedCommandContext.utterance !== utterance
|
||||
&& !validatedCommandContext.utterance.includes(utterance)
|
||||
@@ -83,6 +105,123 @@
|
||||
: null;
|
||||
};
|
||||
|
||||
const monotonicNow = () => (
|
||||
typeof performance !== "undefined" && typeof performance.now === "function"
|
||||
? performance.now()
|
||||
: Date.now()
|
||||
);
|
||||
|
||||
const finiteMilliseconds = (value) => {
|
||||
const numeric = Number(value);
|
||||
return Number.isFinite(numeric) && numeric >= 0 ? numeric : null;
|
||||
};
|
||||
|
||||
const semanticValue = (value) => {
|
||||
if (value == null) return "null";
|
||||
if (["string", "number", "boolean"].includes(typeof value)) return String(value);
|
||||
try {
|
||||
return JSON.stringify(value);
|
||||
} catch (_error) {
|
||||
return String(value);
|
||||
}
|
||||
};
|
||||
|
||||
function semanticControlInstruction(operation = {}) {
|
||||
const capability = String(operation.capability || "unknown");
|
||||
const rawTarget = operation.target || {};
|
||||
const target = rawTarget.zone || rawTarget.instance || rawTarget.device;
|
||||
const argumentsList = [];
|
||||
if (target != null && String(target).trim()) {
|
||||
argumentsList.push(`target=${String(target).replaceAll("_", "-")}`);
|
||||
}
|
||||
Object.entries(operation.parameters || {})
|
||||
.sort(([left], [right]) => left.localeCompare(right))
|
||||
.forEach(([key, value]) => {
|
||||
argumentsList.push(`${key}=${semanticValue(value)}`);
|
||||
});
|
||||
return `${capability}(${argumentsList.join(", ")})`;
|
||||
}
|
||||
|
||||
function commandPlanEntries(result = {}) {
|
||||
const plans = result.command_plans && typeof result.command_plans === "object"
|
||||
? Object.entries(result.command_plans)
|
||||
: [];
|
||||
if (!plans.length && result.command_plan) plans.push(["default", result.command_plan]);
|
||||
return plans;
|
||||
}
|
||||
|
||||
function compiledCommandsForOperation(result, configKey, operation = {}) {
|
||||
const operationId = String(operation.id || "");
|
||||
if (!operationId) return [];
|
||||
const plans = commandPlanEntries(result);
|
||||
const preferred = plans.filter(([key]) => key === configKey);
|
||||
const operationRefs = (command) => {
|
||||
const raw = command?.operation_ref;
|
||||
const refs = Array.isArray(raw) ? raw : String(raw || "").split(",");
|
||||
return refs.map((ref) => String(ref).trim()).filter(Boolean);
|
||||
};
|
||||
const matches = (entries) => entries.flatMap(([planKey, plan]) => (
|
||||
(plan?.spec?.commands || [])
|
||||
.filter((command) => operationRefs(command).includes(operationId))
|
||||
.map((command) => ({ command, plan, planKey }))
|
||||
));
|
||||
if (preferred.length) return matches(preferred);
|
||||
const configurations = result.function_configs && typeof result.function_configs === "object"
|
||||
? Object.entries(result.function_configs)
|
||||
: result.function_config ? [["default", result.function_config]] : [];
|
||||
return configurations.length === 1 && plans.length === 1 ? matches(plans) : [];
|
||||
}
|
||||
|
||||
function compiledCommandPresentation(entries, fallback = {}) {
|
||||
if (!entries.length) return fallback;
|
||||
const actualCommands = [];
|
||||
const frames = [];
|
||||
let channel = fallback.channel || "CAN1";
|
||||
entries.forEach(({ command, plan }) => {
|
||||
const signals = command?.signals || {};
|
||||
const message = String(command?.message || "");
|
||||
const planDomain = String(plan?.spec?.domain || "");
|
||||
const bus = String(command?.bus || "").toLowerCase();
|
||||
const slot = boundedInteger(signals.slot, 1, 248);
|
||||
if (message === "infrared.emit" || planDomain === "self_extension") {
|
||||
channel = "IR";
|
||||
} else if (bus.includes("seat") || bus.includes("can0")) {
|
||||
channel = "CAN0";
|
||||
} else if (bus.includes("body") || bus.includes("can1")) {
|
||||
channel = "CAN1";
|
||||
}
|
||||
const actual = String(signals.debug_command || "").trim()
|
||||
|| (message === "infrared.emit" && slot !== null ? `infrared emit ${slot}` : "")
|
||||
|| String(fallback.command || message).trim();
|
||||
if (actual && !actualCommands.includes(actual)) actualCommands.push(actual);
|
||||
const frameId = String(signals.frame_id || "").trim();
|
||||
const dataHex = String(signals.data_hex || "").trim();
|
||||
if (frameId && dataHex) {
|
||||
frames.push({ label: `${channel} 帧`, value: `${frameId} · ${dataHex}` });
|
||||
} else if (message === "infrared.emit" && slot !== null) {
|
||||
frames.push({
|
||||
label: "GSCU1B TCP",
|
||||
value: JSON.stringify({ action: "emit", data: { no: slot }, type: "infrared" }),
|
||||
});
|
||||
}
|
||||
});
|
||||
const uniqueFrames = frames.filter((frame, index) => (
|
||||
frames.findIndex((candidate) => (
|
||||
candidate.label === frame.label && candidate.value === frame.value
|
||||
)) === index
|
||||
));
|
||||
return {
|
||||
...fallback,
|
||||
channel,
|
||||
command: actualCommands.join(";") || fallback.command || "",
|
||||
...(uniqueFrames.length ? {
|
||||
frame: uniqueFrames[0].value,
|
||||
frameLabel: uniqueFrames[0].label,
|
||||
frames: uniqueFrames,
|
||||
} : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function parsedCanCommandForOperation(operation = {}) {
|
||||
const capability = String(operation.capability || "");
|
||||
const parameters = operation.parameters || {};
|
||||
@@ -397,15 +536,68 @@
|
||||
};
|
||||
};
|
||||
const commands = [];
|
||||
configurationEntries.forEach(([_configKey, configuration]) => {
|
||||
(configuration?.spec?.operations || []).forEach((operation) => {
|
||||
const item = parsedCanCommandForOperation(operation);
|
||||
if (item) commands.push({ ...item, ...deliveryForOperation(operation) });
|
||||
const validatedControlCommands = Array.isArray(result.validated_control_commands)
|
||||
? result.validated_control_commands
|
||||
: [];
|
||||
if (validatedControlCommands.length) {
|
||||
validatedControlCommands.forEach((presentation) => {
|
||||
const operation = presentation?.operation || {};
|
||||
const fallback = parsedCanCommandForOperation(operation) || {
|
||||
channel: presentation?.domain === "self_extension" ? "IR" : "CAN1",
|
||||
category: String(operation.capability || "控制指令"),
|
||||
command: "",
|
||||
...(operation.parse_source ? { parseSource: String(operation.parse_source) } : {}),
|
||||
};
|
||||
const channel = new Set(["CAN0", "CAN1", "IR"]).has(presentation?.channel)
|
||||
? presentation.channel
|
||||
: fallback.channel;
|
||||
const frames = (Array.isArray(presentation?.frames) ? presentation.frames : [])
|
||||
.map((frame) => ({
|
||||
label: String(frame?.label || "控制帧"),
|
||||
value: String(frame?.value || ""),
|
||||
}))
|
||||
.filter((frame) => frame.value);
|
||||
const command = String(presentation?.actual_command || fallback.command || "").trim();
|
||||
if (!command) return;
|
||||
commands.push({
|
||||
...fallback,
|
||||
channel,
|
||||
command,
|
||||
...(frames.length ? {
|
||||
frame: frames[0].value,
|
||||
frameLabel: frames[0].label,
|
||||
frames,
|
||||
} : {}),
|
||||
semantic: semanticControlInstruction(operation),
|
||||
operationRef: String(operation.id || ""),
|
||||
...deliveryForOperation(operation),
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
configurationEntries.forEach(([configKey, configuration]) => {
|
||||
(configuration?.spec?.operations || []).forEach((operation) => {
|
||||
const fallback = parsedCanCommandForOperation(operation) || {
|
||||
channel: configuration?.spec?.domain === "self_extension" ? "IR" : "CAN1",
|
||||
category: String(operation.capability || "控制指令"),
|
||||
command: "",
|
||||
...(operation.parse_source ? { parseSource: String(operation.parse_source) } : {}),
|
||||
};
|
||||
const compiled = compiledCommandsForOperation(result, configKey, operation);
|
||||
const item = compiledCommandPresentation(compiled, fallback);
|
||||
if (item.command) {
|
||||
commands.push({
|
||||
...item,
|
||||
semantic: semanticControlInstruction(operation),
|
||||
operationRef: String(operation.id || ""),
|
||||
...deliveryForOperation(operation),
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
const unique = new Map();
|
||||
commands.forEach((item) => {
|
||||
const key = `${item.channel}:${item.command}`;
|
||||
const key = `${item.operationRef || item.semantic}:${item.channel}:${item.command}`;
|
||||
const previous = unique.get(key);
|
||||
if (!previous
|
||||
|| (previous.deliveryState !== "submitted" && item.deliveryState === "submitted")
|
||||
@@ -425,7 +617,192 @@
|
||||
}[source] || "云端模型";
|
||||
}
|
||||
|
||||
function renderParsedCanCommands(result, { message = "" } = {}) {
|
||||
const commandTraceStageLabels = {
|
||||
intent_parse: "意图解析",
|
||||
context_resolve: "上下文解析",
|
||||
domain_route: "控制域路由",
|
||||
function_generate: "语义指令生成",
|
||||
schema_validate: "Schema 校验",
|
||||
domain_validate: "领域语义校验",
|
||||
compile: "实际指令编译",
|
||||
nfc_peripheral_route: "红外能力路由",
|
||||
local_peripheral_plan: "红外本地规则编译",
|
||||
nfc_peripheral_sequence: "红外指令序列生成",
|
||||
};
|
||||
|
||||
const speechTimingLabels = {
|
||||
audio_read_ms: "音频读取与重采样",
|
||||
vad_ms: "语音活动检测",
|
||||
asr_ms: "语音识别 ASR",
|
||||
assembly_ms: "分段文本组装",
|
||||
semantic_ms: "语音语义处理",
|
||||
command_resolution_ms: "本地指令消歧",
|
||||
recovery_ms: "语音识别恢复",
|
||||
total_ms: "语音服务总耗时",
|
||||
};
|
||||
|
||||
function parsedCommandTimingRows(result = {}, timingState = {}) {
|
||||
const rows = [];
|
||||
const push = (group, key, label, value) => {
|
||||
const durationMs = finiteMilliseconds(value);
|
||||
if (durationMs !== null) rows.push({ group, key, label, durationMs });
|
||||
};
|
||||
if (timingState.inputMode === "speech") {
|
||||
Object.entries(speechTimingLabels).forEach(([key, label]) => {
|
||||
push("speech", key, label, timingState.speechTimings?.[key]);
|
||||
});
|
||||
if (timingState.speechStartedAt != null && timingState.speechResultAt != null) {
|
||||
push(
|
||||
"speech",
|
||||
"speech_capture_to_text",
|
||||
"语音开始 → 文本就绪",
|
||||
timingState.speechResultAt - timingState.speechStartedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
const traceEvents = Array.isArray(result.trace_events) ? result.trace_events : [];
|
||||
const compileIndex = traceEvents.map((event) => event?.stage).lastIndexOf("compile");
|
||||
const validatedTraceEvents = compileIndex >= 0
|
||||
? traceEvents.slice(0, compileIndex + 1)
|
||||
: traceEvents;
|
||||
const localRuleResult = String(result.provider_used || "").startsWith("local")
|
||||
|| parsedCanCommands(result).some((item) => item.parseSource === "local_rule");
|
||||
const localBudgetMs = finiteMilliseconds(result.local_control_budget_ms);
|
||||
const localWithinBudget = result.local_control_within_budget === true;
|
||||
push(
|
||||
"backend",
|
||||
"local_control_elapsed",
|
||||
localBudgetMs === null
|
||||
? "本地规则解析与下发"
|
||||
: `本地规则解析与下发(${localWithinBudget ? "1 秒内" : "超出 1 秒"})`,
|
||||
result.local_control_elapsed_ms,
|
||||
);
|
||||
validatedTraceEvents.forEach((event, index) => {
|
||||
const stage = String(event?.stage || "");
|
||||
if (!(stage in commandTraceStageLabels)) return;
|
||||
const label = stage === "intent_parse" && localRuleResult
|
||||
? "本地规则意图解析"
|
||||
: commandTraceStageLabels[stage];
|
||||
push("backend", `${stage}-${index}`, label, event.duration_ms);
|
||||
});
|
||||
if (timingState.requestStartedAt != null && timingState.validatedAt != null) {
|
||||
push(
|
||||
"frontend",
|
||||
"request_to_validated",
|
||||
"请求提交 → 编译结果到达",
|
||||
timingState.validatedAt - timingState.requestStartedAt,
|
||||
);
|
||||
}
|
||||
push("frontend", "dom_render", "前端指令渲染", timingState.renderDurationMs);
|
||||
if (timingState.requestStartedAt != null && timingState.displayedAt != null) {
|
||||
push(
|
||||
"total",
|
||||
"request_to_display",
|
||||
"文本提交 → 指令显示",
|
||||
timingState.displayedAt - timingState.requestStartedAt,
|
||||
);
|
||||
}
|
||||
if (timingState.inputMode === "speech"
|
||||
&& timingState.speechStartedAt != null
|
||||
&& timingState.displayedAt != null) {
|
||||
push(
|
||||
"total",
|
||||
"speech_to_display",
|
||||
"语音开始 → 指令显示",
|
||||
timingState.displayedAt - timingState.speechStartedAt,
|
||||
);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
const formattedDuration = (value) => {
|
||||
const duration = finiteMilliseconds(value);
|
||||
if (duration === null) return "--";
|
||||
if (duration > 0 && duration < 0.1) return "<0.1 ms";
|
||||
return `${duration < 10 ? duration.toFixed(1) : Math.round(duration)} ms`;
|
||||
};
|
||||
|
||||
function renderCommandTimings(result = null, { pending = false } = {}) {
|
||||
const container = $("#v2CommandTiming");
|
||||
if (!container) return;
|
||||
const rows = result ? parsedCommandTimingRows(result, commandTimingState) : [];
|
||||
if (!rows.length && !pending) {
|
||||
container.hidden = true;
|
||||
container.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
container.hidden = false;
|
||||
if (!rows.length) {
|
||||
container.innerHTML = `<p>阶段耗时计时中 · ${commandTimingState.inputMode === "speech" ? "语音输入" : "普通文本"}</p>`;
|
||||
return;
|
||||
}
|
||||
const total = [...rows].reverse().find((row) => row.group === "total");
|
||||
container.innerHTML = `
|
||||
<header>
|
||||
<strong>阶段耗时</strong>
|
||||
<span>${commandTimingState.inputMode === "speech" ? "语音输入" : "普通文本"}</span>
|
||||
${total ? `<b>${escapeHtml(formattedDuration(total.durationMs))}</b>` : ""}
|
||||
</header>
|
||||
<ol>${rows.map((row) => `
|
||||
<li data-timing-group="${escapeHtml(row.group)}" data-timing-stage="${escapeHtml(row.key)}">
|
||||
<span>${escapeHtml(row.label)}</span><b>${escapeHtml(formattedDuration(row.durationMs))}</b>
|
||||
</li>`).join("")}</ol>`;
|
||||
}
|
||||
|
||||
function beginCommandTiming(request = {}) {
|
||||
const input = $("#utterance");
|
||||
const utterance = String(request.utterance ?? input?.value ?? "").trim();
|
||||
const speechUtteranceId = String(
|
||||
request.speech_utterance_id ?? input?.dataset?.speechUtteranceId ?? "",
|
||||
);
|
||||
const speechValue = String(
|
||||
request.speech_value ?? input?.dataset?.speechValue ?? "",
|
||||
);
|
||||
const speechMatch = Boolean(
|
||||
speechUtteranceId
|
||||
&& pendingSpeechTiming?.utteranceId === speechUtteranceId
|
||||
&& speechValue === utterance,
|
||||
);
|
||||
commandTimingState = {
|
||||
inputMode: speechMatch ? "speech" : "text",
|
||||
utterance,
|
||||
requestId: "",
|
||||
requestStartedAt: monotonicNow(),
|
||||
validatedAt: null,
|
||||
displayedAt: null,
|
||||
renderDurationMs: null,
|
||||
speechStartedAt: speechMatch ? pendingSpeechTiming.startedAt : null,
|
||||
speechResultAt: speechMatch ? pendingSpeechTiming.resultAt : null,
|
||||
speechTimings: speechMatch ? { ...(pendingSpeechTiming.timings || {}) } : null,
|
||||
};
|
||||
}
|
||||
|
||||
function commandResultMatchesTimedRequest(result = {}) {
|
||||
if (commandTimingState.requestStartedAt == null) return true;
|
||||
const expectedUtterance = String(commandTimingState.utterance || "")
|
||||
.replace(/\s+/g, "")
|
||||
.trim();
|
||||
const receivedUtterance = String(result.utterance || "")
|
||||
.replace(/\s+/g, "")
|
||||
.trim();
|
||||
if (expectedUtterance && receivedUtterance
|
||||
&& expectedUtterance !== receivedUtterance
|
||||
&& !expectedUtterance.includes(receivedUtterance)
|
||||
&& !receivedUtterance.includes(expectedUtterance)) return false;
|
||||
const requestIds = commandCorrelationIds(result);
|
||||
if (commandTimingState.requestId && requestIds.length
|
||||
&& !requestIds.includes(commandTimingState.requestId)) return false;
|
||||
if (!commandTimingState.requestId && requestIds.length) {
|
||||
[commandTimingState.requestId] = requestIds;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function renderParsedCanCommands(
|
||||
result,
|
||||
{ message = "", pending = false, recordTiming = false, reveal = false } = {},
|
||||
) {
|
||||
const renderStartedAt = monotonicNow();
|
||||
const panel = $("#v2CanCommandPanel");
|
||||
const list = $("#v2CanCommandList");
|
||||
const count = $("#v2CanCommandCount");
|
||||
@@ -442,12 +819,31 @@
|
||||
<b class="v2-can-command-delivery" data-delivery-state="${escapeHtml(item.deliveryState)}" title="${escapeHtml(`${item.deliveryTransport} · ${item.deliveryLabel}`)}">${escapeHtml(item.deliveryLabel)}</b>
|
||||
<i class="v2-can-command-category">${escapeHtml(item.category)}</i>
|
||||
</span>
|
||||
<code>${escapeHtml(item.command)}</code>
|
||||
${item.frame ? `<small class="v2-can-command-frame">${escapeHtml(item.frameLabel || "固件原生 CAN0")} · ${escapeHtml(item.frame)}</small>` : ""}
|
||||
<div class="v2-can-command-layer v2-can-command-semantic">
|
||||
<small>语义控制指令</small>
|
||||
<code>${escapeHtml(item.semantic)}</code>
|
||||
</div>
|
||||
<div class="v2-can-command-layer v2-can-command-actual">
|
||||
<small>实际控制指令</small>
|
||||
<code>${escapeHtml(item.command)}</code>
|
||||
</div>
|
||||
${(item.frames || (item.frame ? [{ label: item.frameLabel || "控制帧", value: item.frame }] : [])).map((frame) => `<small class="v2-can-command-frame"><b>${escapeHtml(frame.label)}</b> · ${escapeHtml(frame.value)}</small>`).join("")}
|
||||
</li>`).join("")}</ol>`;
|
||||
} else {
|
||||
list.innerHTML = `<p>${escapeHtml(message || "本次请求未解析出控制指令")}</p>`;
|
||||
}
|
||||
if (reveal && commands.length) {
|
||||
panel.open = true;
|
||||
if (ui.sidebarCollapsed) setSidebarCollapsed(false);
|
||||
}
|
||||
if (recordTiming && commands.length) {
|
||||
if (commandTimingState.validatedAt == null) commandTimingState.validatedAt = renderStartedAt;
|
||||
if (commandTimingState.displayedAt == null) {
|
||||
commandTimingState.displayedAt = monotonicNow();
|
||||
commandTimingState.renderDurationMs = commandTimingState.displayedAt - renderStartedAt;
|
||||
}
|
||||
}
|
||||
renderCommandTimings(result, { pending });
|
||||
requestAnimationFrame(syncSidebarLogBaseline);
|
||||
}
|
||||
|
||||
@@ -767,6 +1163,11 @@
|
||||
logKeys: new Set(),
|
||||
extensionFingerprint: "",
|
||||
};
|
||||
const nonDispatchTaskSources = new Set(["mode_editor_compile"]);
|
||||
|
||||
function isDispatchTask(task = {}) {
|
||||
return !nonDispatchTaskSources.has(String(task.source || "").trim());
|
||||
}
|
||||
|
||||
const readText = (selector, fallback = "--") => $(selector)?.textContent?.trim() || fallback;
|
||||
|
||||
@@ -1625,26 +2026,28 @@
|
||||
if (revision !== displaySync.revision) {
|
||||
displaySync.revision = revision;
|
||||
const task = snapshot?.task || {};
|
||||
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} · ${submittedUnverified ? "指令已提交,等待车辆状态确认" : task.status || "idle"}`,
|
||||
status: submittedUnverified ? "info" : ["succeeded", "partially_succeeded"].includes(task.status) ? "success" : task.status === "failed" ? "error" : "info",
|
||||
});
|
||||
}
|
||||
const result = task.result;
|
||||
const requestId = result?.request_id || null;
|
||||
if (result && requestId !== displaySync.requestId) {
|
||||
displaySync.requestId = requestId;
|
||||
window.CognitiveAgentConsole?.applySyncedResult?.(result, {
|
||||
utterance: task.utterance || "",
|
||||
});
|
||||
if (isDispatchTask(task)) {
|
||||
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} · ${submittedUnverified ? "指令已提交,等待车辆状态确认" : task.status || "idle"}`,
|
||||
status: submittedUnverified ? "info" : ["succeeded", "partially_succeeded"].includes(task.status) ? "success" : task.status === "failed" ? "error" : "info",
|
||||
});
|
||||
}
|
||||
const result = task.result;
|
||||
const requestId = result?.request_id || null;
|
||||
if (result && requestId !== displaySync.requestId) {
|
||||
displaySync.requestId = requestId;
|
||||
window.CognitiveAgentConsole?.applySyncedResult?.(result, {
|
||||
utterance: task.utterance || "",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const extensionFingerprint = synchronizedCatalog
|
||||
@@ -1763,12 +2166,13 @@
|
||||
insertVisibleControl(simulationFaultField, "v2-sync-simulation-fault-field");
|
||||
const canCommandPanel = make("details", "v2-can-command-panel", `
|
||||
<summary>
|
||||
<span><i data-lucide="binary"></i><b>已解析控制指令</b></span>
|
||||
<span><i data-lucide="binary"></i><b>下发控制指令</b></span>
|
||||
<strong id="v2CanCommandCount">0</strong>
|
||||
<i data-lucide="chevron-down"></i>
|
||||
</summary>
|
||||
<section class="v2-command-timing" id="v2CommandTiming" aria-live="polite" hidden></section>
|
||||
<div class="v2-can-command-list" id="v2CanCommandList">
|
||||
<p>等待自然语言解析结果</p>
|
||||
<p>等待准备下发的控制指令</p>
|
||||
</div>`);
|
||||
canCommandPanel.id = "v2CanCommandPanel";
|
||||
syncOverview.append(canCommandPanel);
|
||||
@@ -1875,17 +2279,57 @@
|
||||
if (event.target.matches("select[data-device-control]")) storeDeviceControlDraft(event.target);
|
||||
});
|
||||
$("#v2LogFilter")?.addEventListener("change", renderSyncLogs);
|
||||
$("#requestForm")?.addEventListener("submit", () => {
|
||||
const beginPendingControlRequest = (request = {}) => {
|
||||
beginCommandTiming(request);
|
||||
resetValidatedCommandContext();
|
||||
renderParsedCanCommands(null, { message: "正在解析自然语言并生成控制指令…" });
|
||||
renderParsedCanCommands(null, {
|
||||
message: "正在解析自然语言并生成控制指令…",
|
||||
pending: true,
|
||||
});
|
||||
};
|
||||
$("#requestForm")?.addEventListener("submit", () => {
|
||||
beginPendingControlRequest();
|
||||
}, true);
|
||||
window.addEventListener("cognitive-agent:control-request-start", (event) => {
|
||||
beginPendingControlRequest(event.detail || {});
|
||||
});
|
||||
window.addEventListener("cognitive-agent:speech-state", (event) => {
|
||||
const phase = event.detail?.phase;
|
||||
if (phase === "capturing" && !pendingSpeechTiming?.captureActive) {
|
||||
pendingSpeechTiming = {
|
||||
utteranceId: "",
|
||||
startedAt: monotonicNow(),
|
||||
resultAt: null,
|
||||
timings: null,
|
||||
captureActive: true,
|
||||
};
|
||||
} else if (["finalizing", "success", "error"].includes(phase)
|
||||
&& pendingSpeechTiming) {
|
||||
pendingSpeechTiming.captureActive = false;
|
||||
}
|
||||
});
|
||||
window.addEventListener("cognitive-agent:speech-result", (event) => {
|
||||
const result = event.detail || {};
|
||||
pendingSpeechTiming = {
|
||||
...(pendingSpeechTiming || { startedAt: null }),
|
||||
utteranceId: String(result.utterance_id || ""),
|
||||
resultAt: monotonicNow(),
|
||||
timings: result.timings && typeof result.timings === "object"
|
||||
? { ...result.timings }
|
||||
: null,
|
||||
captureActive: false,
|
||||
};
|
||||
});
|
||||
window.addEventListener("cognitive-agent:validated-can-commands", (event) => {
|
||||
const result = event.detail || {};
|
||||
if (!commandResultMatchesTimedRequest(result)) return;
|
||||
validatedCommandsVisible = parsedCanCommands(result).length > 0;
|
||||
if (validatedCommandsVisible) rememberValidatedCommandContext(result);
|
||||
else resetValidatedCommandContext();
|
||||
renderParsedCanCommands(result, {
|
||||
message: "控制指令已解析,正在等待传输结果…",
|
||||
recordTiming: true,
|
||||
reveal: true,
|
||||
});
|
||||
});
|
||||
window.addEventListener("cognitive-agent:result", (event) => {
|
||||
@@ -1893,11 +2337,15 @@
|
||||
const submittedUnverified = result.execution_outcome === "submitted_unverified"
|
||||
|| result.execution_report?.spec?.verification_status === "submitted_unverified";
|
||||
const commands = parsedCanCommands(result);
|
||||
if (commands.length) {
|
||||
const matchesTimedRequest = commandResultMatchesTimedRequest(result);
|
||||
if (matchesTimedRequest && commands.length) {
|
||||
validatedCommandsVisible = true;
|
||||
rememberValidatedCommandContext(result);
|
||||
renderParsedCanCommands(result);
|
||||
} else if (!validatedCommandsBelongToTask({
|
||||
renderParsedCanCommands(result, {
|
||||
recordTiming: commandTimingState.displayedAt == null,
|
||||
reveal: commandTimingState.requestStartedAt != null,
|
||||
});
|
||||
} else if (matchesTimedRequest && !validatedCommandsBelongToTask({
|
||||
utterance: result.utterance,
|
||||
result,
|
||||
})) {
|
||||
@@ -1917,7 +2365,15 @@
|
||||
});
|
||||
});
|
||||
window.addEventListener("cognitive-agent:request-complete", (event) => {
|
||||
if (event.detail?.error && !validatedCommandsVisible) {
|
||||
const completedUtterance = String(event.detail?.utterance || "")
|
||||
.replace(/\s+/g, "")
|
||||
.trim();
|
||||
const expectedUtterance = String(commandTimingState.utterance || "")
|
||||
.replace(/\s+/g, "")
|
||||
.trim();
|
||||
const matchesTimedRequest = !expectedUtterance || !completedUtterance
|
||||
|| expectedUtterance === completedUtterance;
|
||||
if (matchesTimedRequest && !validatedCommandsVisible) {
|
||||
renderParsedCanCommands(null, { message: "本次请求未生成控制指令" });
|
||||
}
|
||||
if (event.detail?.error) {
|
||||
|
||||
Reference in New Issue
Block a user