mqtt切换为远程服务器部署,优化简单指令解析时长

This commit is contained in:
Miayui
2026-09-11 10:22:24 +08:00
parent 51ca12b624
commit 3fc6f2c2a4
34 changed files with 4290 additions and 484 deletions
@@ -195,9 +195,15 @@ def test_left_sidebar_exposes_collapsed_parsed_can_command_panel() -> None:
assert 'canCommandPanel.id = "v2CanCommandPanel"' in console
assert 'id="v2CanCommandCount">0</strong>' in console
assert 'id="v2CanCommandList"' in console
assert "canCommandPanel.open" not in console
assert 'id="v2CommandTiming"' in console
assert "panel.open = true;" in console
assert "if (ui.sidebarCollapsed) setSidebarCollapsed(false);" in console
assert "function parsedCanCommandForOperation(operation = {})" in console
assert "function parsedCanCommands(result = {})" in console
assert "function semanticControlInstruction(operation = {})" in console
assert "function compiledCommandsForOperation(result, configKey, operation = {})" in console
assert "function parsedCommandTimingRows(result = {}, timingState = {})" in console
assert "result.validated_control_commands" in console
assert "getParsedCanCommands: (result) => parsedCanCommands(result)" in console
assert "const configurations = result.function_configs || {};" in console
assert "if (!configurationEntries.length && result.function_config)" in console
@@ -206,8 +212,18 @@ def test_left_sidebar_exposes_collapsed_parsed_can_command_panel() -> None:
assert 'const command = (category, text, channel = "CAN1", frame = "")' in console
assert 'data-can-channel="${escapeHtml(item.channel)}"' in console
assert 'class="v2-can-command-channel"' in console
assert 'class="v2-can-command-layer v2-can-command-semantic"' in console
assert 'class="v2-can-command-layer v2-can-command-actual"' in console
assert console.index("语义控制指令") < console.index("实际控制指令")
assert 'recordTiming: true' in console
assert 'reveal: true' in console
assert 'window.addEventListener("cognitive-agent:speech-state"' in console
assert 'window.addEventListener("cognitive-agent:speech-result"' in console
assert 'window.addEventListener("cognitive-agent:control-request-start"' in console
assert 'window.addEventListener("cognitive-agent:result"' in console
assert '$("#requestForm")?.addEventListener("submit"' in console
assert "function commandResultMatchesTimedRequest(result = {})" in console
assert "matchesTimedRequest && !validatedCommandsVisible" in console
for command_contract in (
"door rear ${action}",
@@ -246,6 +262,9 @@ def test_left_sidebar_exposes_collapsed_parsed_can_command_panel() -> None:
assert ".v2-can-command-list li code" in styles
assert ".v2-can-command-channel" in styles
assert ".v2-can-command-delivery" in styles
assert ".v2-command-timing" in styles
assert ".v2-can-command-semantic" in styles
assert ".v2-can-command-actual" in styles
assert '.v2-can-command-delivery[data-delivery-state="submitted"]' in styles
assert 'li[data-can-channel="CAN0"]' in styles
assert ".v2-sidebar-collapsed .v2-can-command-panel" in styles
@@ -297,10 +316,12 @@ def test_parsed_can_command_panel_combines_can0_seat_and_can1_vehicle_commands()
"process.stdout.write(JSON.stringify(parsedCanCommands(sample)));"
)
completed = subprocess.run(
[node, "-e", script],
[node, "-"],
input=script,
check=True,
capture_output=True,
text=True,
encoding="utf-8",
)
assert json.loads(completed.stdout) == [
@@ -310,6 +331,8 @@ def test_parsed_can_command_panel_combines_can0_seat_and_can1_vehicle_commands()
"command": "seat passenger backrest position 36",
"frame": "0x300 · 01 12 <SEQ_LO> <SEQ_HI> 10 0E 30 75",
"parseSource": "local_rule",
"semantic": "seat.set_backrest_recline(target=front-right, angle_deg=120, position_pct=36)",
"operationRef": "",
"deliveryState": "not_submitted",
"deliveryLabel": "未下发",
"deliveryTransport": "有线 CAN",
@@ -319,6 +342,8 @@ def test_parsed_can_command_panel_combines_can0_seat_and_can1_vehicle_commands()
"category": "空调温度",
"command": "ac temp driver 24",
"parseSource": "cloud_supplement",
"semantic": "climate.set_temperature(target=driver, temperature_c=24)",
"operationRef": "",
"deliveryState": "not_submitted",
"deliveryLabel": "未下发",
"deliveryTransport": "有线 CAN",
@@ -326,6 +351,335 @@ def test_parsed_can_command_panel_combines_can0_seat_and_can1_vehicle_commands()
]
def test_local_mixed_command_uses_compiled_can_and_ir_details_with_stage_timings() -> None:
node = shutil.which("node")
if node is None:
return
console = source("console-v2.js")
start = console.index(" const normalizedCanTarget")
end = console.index("\n\n function renderParsedCanCommands", start)
helpers = console[start:end]
sample = {
"provider_used": "local_rules",
"function_configs": {
"native_climate": {
"spec": {
"domain": "climate",
"operations": [
{
"id": "climate-temperature-1",
"capability": "climate.set_temperature",
"target": {"zone": "driver"},
"parameters": {"temperature_c": 23},
"parse_source": "local_rule",
}
],
}
},
"self_extension": {
"spec": {
"domain": "self_extension",
"operations": [
{
"id": "infrared-1",
"capability": "infrared.emit",
"target": {"device": "toy-001", "name": "闪灯玩具"},
"parameters": {"slot": 1, "action": "开启"},
"parse_source": "local_rule",
}
],
}
},
},
# Deliberately reverse the plan order and include a distracting command;
# the renderer must join only on the exact operation_ref.
"command_plans": {
"self_extension": {
"spec": {
"domain": "self_extension",
"commands": [
{
"operation_ref": "unrelated-ir",
"message": "infrared.emit",
"signals": {"slot": 9},
},
{
"operation_ref": "infrared-1",
"bus": "TCP",
"message": "infrared.emit",
"signals": {"slot": 1},
},
],
}
},
"native_climate": {
"spec": {
"domain": "climate",
"commands": [
{
"operation_ref": "climate-temperature-1",
"bus": "body_can",
"message": "Native_0x1DE",
"signals": {
"debug_command": "ac temp driver 23",
"frame_id": "0x1DE",
"data_hex": "00 00 00 00 17 00 00 00",
},
}
],
}
},
},
"trace_events": [
{"stage": "local_peripheral_plan", "duration_ms": 1},
{"stage": "nfc_peripheral_sequence", "duration_ms": 0},
{"stage": "intent_parse", "duration_ms": 12},
{"stage": "context_resolve", "duration_ms": 1},
{"stage": "compile", "duration_ms": 2},
{"stage": "execute", "duration_ms": 999},
],
}
timing_state = {
"inputMode": "speech",
"speechStartedAt": 0,
"speechResultAt": 80,
"speechTimings": {
"audio_read_ms": 1,
"vad_ms": 2,
"asr_ms": 30,
"assembly_ms": 1,
"semantic_ms": 3,
"command_resolution_ms": 1,
"recovery_ms": 0,
"total_ms": 37,
},
"requestStartedAt": 100,
"validatedAt": 125,
"displayedAt": 126,
"renderDurationMs": 1,
}
browser_projection = {
"provider_used": "local_rules+infrared_local_rules",
"execution_mode": "dry_run",
"validated_control_commands": [
{
"operation": sample["function_configs"]["native_climate"]["spec"][
"operations"
][0],
"domain": "climate",
"channel": "CAN1",
"actual_command": "ac temp driver 23",
"frames": [
{
"label": "CAN1 帧",
"value": "0x1DE · 00 00 00 00 17 00 00 00",
}
],
},
{
"operation": sample["function_configs"]["self_extension"]["spec"][
"operations"
][0],
"domain": "self_extension",
"channel": "IR",
"actual_command": "infrared emit 1",
"frames": [
{
"label": "GSCU1B TCP",
"value": '{"action":"emit","data":{"no":1},"type":"infrared"}',
}
],
},
],
"trace_events": sample["trace_events"][:3],
}
script = (
f"{helpers}\n"
f"const sample = {json.dumps(sample, ensure_ascii=False)};\n"
f"const browserProjection = {json.dumps(browser_projection, ensure_ascii=False)};\n"
f"const timingState = {json.dumps(timing_state)};\n"
"process.stdout.write(JSON.stringify({"
"commands: parsedCanCommands(sample), "
"projectedCommands: parsedCanCommands(browserProjection), "
"timings: parsedCommandTimingRows(sample, timingState)"
"}));"
)
completed = subprocess.run(
[node, "-"],
input=script,
check=True,
capture_output=True,
text=True,
encoding="utf-8",
)
parsed = json.loads(completed.stdout)
climate, infrared = parsed["commands"]
projected_climate, projected_infrared = parsed["projectedCommands"]
assert climate["semantic"] == (
"climate.set_temperature(target=driver, temperature_c=23)"
)
assert climate["command"] == "ac temp driver 23"
assert climate["frame"] == "0x1DE · 00 00 00 00 17 00 00 00"
assert climate["frameLabel"] == "CAN1 帧"
assert climate["parseSource"] == "local_rule"
assert infrared["semantic"] == (
"infrared.emit(target=toy-001, action=开启, slot=1)"
)
assert infrared["command"] == "infrared emit 1"
assert infrared["frameLabel"] == "GSCU1B TCP"
assert json.loads(infrared["frame"])["data"]["no"] == 1
assert infrared["parseSource"] == "local_rule"
assert projected_climate["semantic"] == climate["semantic"]
assert projected_climate["command"] == climate["command"]
assert projected_climate["frames"] == climate["frames"]
assert projected_infrared["semantic"] == infrared["semantic"]
assert projected_infrared["command"] == infrared["command"]
assert projected_infrared["frames"] == infrared["frames"]
timing_by_key = {item["key"]: item for item in parsed["timings"]}
assert timing_by_key["local_peripheral_plan-0"]["label"] == "红外本地规则编译"
assert timing_by_key["nfc_peripheral_sequence-1"]["label"] == "红外指令序列生成"
assert timing_by_key["intent_parse-2"]["label"] == "本地规则意图解析"
assert timing_by_key["request_to_validated"]["durationMs"] == 25
assert timing_by_key["dom_render"]["durationMs"] == 1
assert timing_by_key["request_to_display"]["durationMs"] == 26
assert timing_by_key["speech_to_display"]["durationMs"] == 126
assert timing_by_key["total_ms"]["durationMs"] == 37
assert all(item["durationMs"] >= 0 for item in parsed["timings"])
assert not any(item["key"].startswith("execute") for item in parsed["timings"])
def test_duplicate_operation_refs_do_not_borrow_another_configs_plan() -> None:
node = shutil.which("node")
if node is None:
return
console = source("console-v2.js")
start = console.index(" const normalizedCanTarget")
end = console.index("\n\n function renderParsedCanCommands", start)
helpers = console[start:end]
sample = {
"function_configs": {
"native_climate": {
"spec": {
"domain": "climate",
"operations": [
{
"id": "op-1",
"capability": "climate.set_temperature",
"target": {"zone": "driver"},
"parameters": {"temperature_c": 23},
}
],
}
},
"native_seat": {
"spec": {
"domain": "seat",
"operations": [
{
"id": "op-1",
"capability": "seat.set_heating",
"target": {"zone": "passenger"},
"parameters": {"level": 2},
}
],
}
},
},
"command_plans": {
"native_seat": {
"spec": {
"domain": "seat",
"commands": [
{
"operation_ref": "op-1",
"bus": "seat_can0",
"signals": {
"debug_command": "seat passenger heat 2",
"frame_id": "0x300",
"data_hex": "01 02 03 04 05 06 07 08",
},
}
],
}
}
},
}
script = (
f"{helpers}\n"
f"const sample = {json.dumps(sample)};\n"
"process.stdout.write(JSON.stringify(parsedCanCommands(sample)));"
)
completed = subprocess.run(
[node, "-"],
input=script,
check=True,
capture_output=True,
text=True,
encoding="utf-8",
)
parsed = json.loads(completed.stdout)
by_semantic = {item["semantic"]: item for item in parsed}
climate = by_semantic[
"climate.set_temperature(target=driver, temperature_c=23)"
]
seat = by_semantic["seat.set_heating(target=passenger, level=2)"]
assert climate["command"] == "ac temp driver 23"
assert "frame" not in climate
assert climate["channel"] == "CAN1"
assert seat["command"] == "seat passenger heat 2"
assert seat["channel"] == "CAN0"
def test_command_timing_rejects_stale_request_events() -> None:
node = shutil.which("node")
if node is None:
return
console = source("console-v2.js")
correlation_start = console.index(" function commandCorrelationIds")
correlation_end = console.index(
"\n\n function rememberValidatedCommandContext",
correlation_start,
)
correlation_helper = console[correlation_start:correlation_end]
start = console.index(" const normalizedCanTarget")
end = console.index("\n\n function renderParsedCanCommands", start)
helpers = console[start:end]
script = (
"let commandTimingState = {requestStartedAt: 1, requestId: '', "
"utterance: '空调温度设置到23度,打开闪烁灯'};\n"
f"{correlation_helper}\n"
f"{helpers}\n"
"const first = commandResultMatchesTimedRequest({request_id: 'req-current', "
"utterance: '空调温度设置到23度,打开闪烁灯'});\n"
"const staleId = commandResultMatchesTimedRequest({request_id: 'req-old', "
"utterance: '空调温度设置到23度,打开闪烁灯'});\n"
"const staleText = commandResultMatchesTimedRequest({request_id: 'req-current', "
"utterance: '打开车窗'});\n"
"process.stdout.write(JSON.stringify({first, staleId, staleText, "
"boundRequestId: commandTimingState.requestId}));"
)
completed = subprocess.run(
[node, "-"],
input=script,
check=True,
capture_output=True,
text=True,
encoding="utf-8",
)
assert json.loads(completed.stdout) == {
"first": True,
"staleId": False,
"staleText": False,
"boundRequestId": "req-current",
}
def test_parsed_command_delivery_is_independent_of_transport_and_target() -> None:
node = shutil.which("node")
if node is None:
@@ -398,10 +752,12 @@ def test_parsed_command_delivery_is_independent_of_transport_and_target() -> Non
"parsedCanCommands(sample))));"
)
completed = subprocess.run(
[node, "-e", script],
[node, "-"],
input=script,
check=True,
capture_output=True,
text=True,
encoding="utf-8",
)
parsed = json.loads(completed.stdout)
@@ -423,6 +779,7 @@ def test_parsed_command_delivery_is_independent_of_transport_and_target() -> Non
def test_mode_editor_renders_readable_commands_with_expandable_mqtt_sequences() -> None:
app = source("app.js")
console = source("console-v2.js")
styles = source("styles.css")
html = source("index.html")
@@ -442,6 +799,10 @@ def test_mode_editor_renders_readable_commands_with_expandable_mqtt_sequences()
assert 'executionMode: "dry_run"' in app
assert 'clientType: "mode_editor_compile"' in app
assert "previewOnly: true" in app
assert 'const nonDispatchTaskSources = new Set(["mode_editor_compile"])' in console
assert "if (isDispatchTask(task))" in console
assert "下发控制指令" in console
assert "已解析控制指令" not in console
assert "function executeSavedModeProfile(mode)" in app
assert "/api/mode-profiles/${encodeURIComponent(mode.id)}/execute" in app
assert '$("#utterance").value = mode.name || mode.id;' in app
@@ -450,6 +811,9 @@ def test_mode_editor_renders_readable_commands_with_expandable_mqtt_sequences()
assert "生成指令" in html
assert ">执行</span></button>" not in html
assert 'id="hardwareNote"' not in html
assert 'id="hardwareAliasNote"' not in html
assert 'id="vehicleTransportNote"' not in html
assert ".mode-editor-command-card" in styles
assert ".mode-editor-command-meta" in styles
@@ -544,10 +908,12 @@ def test_mode_editor_prepares_one_to_many_mqtt_message_sequences() -> None:
"process.stdout.write(JSON.stringify(modeEditorReadableCommands(flow)));"
)
completed = subprocess.run(
[node, "-e", script],
[node, "-"],
input=script,
check=True,
capture_output=True,
text=True,
encoding="utf-8",
)
commands = json.loads(completed.stdout)
@@ -619,10 +985,12 @@ def test_left_sidebar_extracts_speech_and_peripheral_control_commands() -> None:
"}));"
)
completed = subprocess.run(
[node, "-e", script],
[node, "-"],
input=script,
check=True,
capture_output=True,
text=True,
encoding="utf-8",
)
output = json.loads(completed.stdout)
@@ -924,7 +1292,8 @@ def test_assistant_mode_preview_groups_validated_actions_for_the_card() -> None:
"process.stdout.write(JSON.stringify(modeDraftRows(draft, actions)));"
)
completed = subprocess.run(
[node, "-e", script],
[node, "-"],
input=script,
check=True,
capture_output=True,
text=True,