feat: route mode cards through AI assistant
This commit is contained in:
+23
-11
@@ -6417,22 +6417,34 @@ async function executeSavedModeProfile(mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyModeCommand(command, modeProfileId = null) {
|
function applyModeCommand(command, modeProfileId = null) {
|
||||||
if (!command) return;
|
const modeCommand = String(command || "").trim();
|
||||||
if ($("#runButton").disabled) {
|
if (!modeCommand) return false;
|
||||||
|
const assistant = window.CognitiveAgentAssistant;
|
||||||
|
if (assistant?.isPending?.()) {
|
||||||
|
showToast("AI助手正在处理上一条指令,请稍候");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($("#runButton")?.disabled) {
|
||||||
showToast("上一条指令仍在执行,请稍候");
|
showToast("上一条指令仍在执行,请稍候");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
const mode = modeProfiles().find((item) => item.id === modeProfileId);
|
const mode = modeProfiles().find((item) => item.id === modeProfileId);
|
||||||
setModeRailSelection(mode);
|
setModeRailSelection(mode);
|
||||||
if (mode?.command_flow?.source_request_id) {
|
|
||||||
void executeSavedModeProfile(mode);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
closeModeGallery({ restoreFocus: false });
|
closeModeGallery({ restoreFocus: false });
|
||||||
state.pendingModeProfileId = modeProfileId;
|
assistant?.open?.();
|
||||||
if (modeProfileId) setModeExecutionState(modeProfileId, "running");
|
const input = $("#utterance");
|
||||||
$("#utterance").value = command;
|
const form = $("#requestForm");
|
||||||
$("#requestForm").requestSubmit();
|
if (!input || !form) {
|
||||||
|
showToast("AI助手暂时不可用,请稍后重试");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
input.value = modeCommand;
|
||||||
|
delete input.dataset.speechStage;
|
||||||
|
delete input.dataset.speechUtteranceId;
|
||||||
|
delete input.dataset.speechValue;
|
||||||
|
input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
form.requestSubmit();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyRuntimeTargetPolicy(runtime) {
|
function applyRuntimeTargetPolicy(runtime) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<script src="/vendor/three/0.147.0/fflate.min.js" defer></script>
|
<script src="/vendor/three/0.147.0/fflate.min.js" defer></script>
|
||||||
<script src="/vendor/three/0.147.0/FBXLoader.js" defer></script>
|
<script src="/vendor/three/0.147.0/FBXLoader.js" defer></script>
|
||||||
<script src="/network.js?v=android-pad-2" defer></script>
|
<script src="/network.js?v=android-pad-2" defer></script>
|
||||||
<script src="/app.js?v=dual-seat-128" defer></script>
|
<script src="/app.js?v=dual-seat-129" defer></script>
|
||||||
<script src="/speech-input.js?v=sensevoice-13" defer></script>
|
<script src="/speech-input.js?v=sensevoice-13" defer></script>
|
||||||
<script type="module" src="/gesture-control.js?v=android-pad-9"></script>
|
<script type="module" src="/gesture-control.js?v=android-pad-9"></script>
|
||||||
<script src="/pwa.js?v=android-pad-3" defer></script>
|
<script src="/pwa.js?v=android-pad-3" defer></script>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const CACHE_VERSION = "cognitive-agent-pad-v275";
|
const CACHE_VERSION = "cognitive-agent-pad-v276";
|
||||||
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
|
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
|
||||||
const RUNTIME_CACHE = `${CACHE_VERSION}-runtime`;
|
const RUNTIME_CACHE = `${CACHE_VERSION}-runtime`;
|
||||||
const APP_SHELL = [
|
const APP_SHELL = [
|
||||||
@@ -6,7 +6,7 @@ const APP_SHELL = [
|
|||||||
"/index.html",
|
"/index.html",
|
||||||
"/styles.css?v=dual-seat-23",
|
"/styles.css?v=dual-seat-23",
|
||||||
"/network.js?v=android-pad-2",
|
"/network.js?v=android-pad-2",
|
||||||
"/app.js?v=dual-seat-128",
|
"/app.js?v=dual-seat-129",
|
||||||
"/gesture-control.js?v=android-pad-9",
|
"/gesture-control.js?v=android-pad-9",
|
||||||
"/pwa.js?v=android-pad-3",
|
"/pwa.js?v=android-pad-3",
|
||||||
"/console-v2.css?v=delivery-state-66",
|
"/console-v2.css?v=delivery-state-66",
|
||||||
|
|||||||
@@ -621,7 +621,7 @@ operations:
|
|||||||
def test_static_console_and_runtime_endpoint(self) -> None:
|
def test_static_console_and_runtime_endpoint(self) -> None:
|
||||||
with self.opener.open(f"{self.base_url}/", timeout=3) as response:
|
with self.opener.open(f"{self.base_url}/", timeout=3) as response:
|
||||||
html = response.read().decode("utf-8")
|
html = response.read().decode("utf-8")
|
||||||
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-128", timeout=3) as response:
|
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-129", timeout=3) as response:
|
||||||
app_script = response.read().decode("utf-8")
|
app_script = response.read().decode("utf-8")
|
||||||
with self.opener.open(
|
with self.opener.open(
|
||||||
f"{self.base_url}/gesture-control.js?v=android-pad-9", timeout=3
|
f"{self.base_url}/gesture-control.js?v=android-pad-9", timeout=3
|
||||||
@@ -697,7 +697,7 @@ operations:
|
|||||||
self.assertIn("CMD 0x431 · FB 0x41A · CH1", html)
|
self.assertIn("CMD 0x431 · FB 0x41A · CH1", html)
|
||||||
self.assertIn("CMD 0x431 · FB 0x388 · CH1", html)
|
self.assertIn("CMD 0x431 · FB 0x388 · CH1", html)
|
||||||
self.assertNotIn("透明车体 · 实体设备", html)
|
self.assertNotIn("透明车体 · 实体设备", html)
|
||||||
self.assertIn('src="/app.js?v=dual-seat-128"', html)
|
self.assertIn('src="/app.js?v=dual-seat-129"', html)
|
||||||
self.assertIn('href="/styles.css?v=dual-seat-23"', html)
|
self.assertIn('href="/styles.css?v=dual-seat-23"', html)
|
||||||
self.assertIn('src="/gesture-control.js?v=android-pad-9"', html)
|
self.assertIn('src="/gesture-control.js?v=android-pad-9"', html)
|
||||||
self.assertNotIn('class="vehicle-system-hud"', html)
|
self.assertNotIn('class="vehicle-system-hud"', html)
|
||||||
@@ -992,14 +992,14 @@ operations:
|
|||||||
self.assertIn("CAN 指令已提交,等待车辆状态确认", console_script)
|
self.assertIn("CAN 指令已提交,等待车辆状态确认", console_script)
|
||||||
self.assertIn('canvas.addEventListener("webglcontextlost"', app_script)
|
self.assertIn('canvas.addEventListener("webglcontextlost"', app_script)
|
||||||
self.assertIn('"cognitive-agent:module-change"', app_script)
|
self.assertIn('"cognitive-agent:module-change"', app_script)
|
||||||
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v275"', service_worker)
|
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v276"', service_worker)
|
||||||
self.assertIn('/console-v2.css?v=delivery-state-66', service_worker)
|
self.assertIn('/console-v2.css?v=delivery-state-66', service_worker)
|
||||||
self.assertIn('/console-v2.js?v=delivery-state-62', service_worker)
|
self.assertIn('/console-v2.js?v=delivery-state-62', service_worker)
|
||||||
self.assertIn('/assistant-chat.css?v=codex-assistant-26', service_worker)
|
self.assertIn('/assistant-chat.css?v=codex-assistant-26', service_worker)
|
||||||
self.assertIn('/assistant-chat.js?v=codex-assistant-26', service_worker)
|
self.assertIn('/assistant-chat.js?v=codex-assistant-26', service_worker)
|
||||||
self.assertIn('/energy-monitor.css?v=energy-monitor-4', service_worker)
|
self.assertIn('/energy-monitor.css?v=energy-monitor-4', service_worker)
|
||||||
self.assertIn('/energy-monitor.js?v=energy-monitor-4', service_worker)
|
self.assertIn('/energy-monitor.js?v=energy-monitor-4', service_worker)
|
||||||
self.assertIn('"/app.js?v=dual-seat-128"', service_worker)
|
self.assertIn('"/app.js?v=dual-seat-129"', service_worker)
|
||||||
self.assertIn('"/speech-input.js?v=sensevoice-13"', service_worker)
|
self.assertIn('"/speech-input.js?v=sensevoice-13"', service_worker)
|
||||||
self.assertIn('"/speech-input.css?v=sensevoice-7"', service_worker)
|
self.assertIn('"/speech-input.css?v=sensevoice-7"', service_worker)
|
||||||
self.assertIn('"/speech-audio-worklet.js?v=sensevoice-5"', service_worker)
|
self.assertIn('"/speech-audio-worklet.js?v=sensevoice-5"', service_worker)
|
||||||
@@ -1456,7 +1456,7 @@ operations:
|
|||||||
) as response:
|
) as response:
|
||||||
extension_script = response.read().decode("utf-8")
|
extension_script = response.read().decode("utf-8")
|
||||||
with self.opener.open(
|
with self.opener.open(
|
||||||
f"{self.base_url}/app.js?v=dual-seat-128", timeout=3
|
f"{self.base_url}/app.js?v=dual-seat-129", timeout=3
|
||||||
) as response:
|
) as response:
|
||||||
app_script = response.read().decode("utf-8")
|
app_script = response.read().decode("utf-8")
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ def test_console_contains_ai_assistant_surface():
|
|||||||
|
|
||||||
def test_codex_assistant_assets_are_versioned_together_in_the_service_worker():
|
def test_codex_assistant_assets_are_versioned_together_in_the_service_worker():
|
||||||
service_worker = (ROOT / "apps/web-console/service-worker.js").read_text(encoding="utf-8")
|
service_worker = (ROOT / "apps/web-console/service-worker.js").read_text(encoding="utf-8")
|
||||||
assert 'const CACHE_VERSION = "cognitive-agent-pad-v275"' in service_worker
|
assert 'const CACHE_VERSION = "cognitive-agent-pad-v276"' in service_worker
|
||||||
assert '"/assistant-chat.css?v=codex-assistant-26"' in service_worker
|
assert '"/assistant-chat.css?v=codex-assistant-26"' in service_worker
|
||||||
assert '"/assistant-chat.js?v=codex-assistant-26"' in service_worker
|
assert '"/assistant-chat.js?v=codex-assistant-26"' in service_worker
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def test_energy_monitor_assets_are_versioned_and_cached() -> None:
|
|||||||
assert html.index("/assistant-chat.js?v=codex-assistant-26") < html.index(
|
assert html.index("/assistant-chat.js?v=codex-assistant-26") < html.index(
|
||||||
"/energy-monitor.js?v=energy-monitor-4"
|
"/energy-monitor.js?v=energy-monitor-4"
|
||||||
)
|
)
|
||||||
assert 'const CACHE_VERSION = "cognitive-agent-pad-v275"' in service_worker
|
assert 'const CACHE_VERSION = "cognitive-agent-pad-v276"' in service_worker
|
||||||
|
|
||||||
|
|
||||||
def test_energy_monitor_uses_the_fifth_shared_dock_icon_and_starts_closed() -> None:
|
def test_energy_monitor_uses_the_fifth_shared_dock_icon_and_starts_closed() -> None:
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ def test_theme_asset_versions_are_consistent_with_the_pwa_shell() -> None:
|
|||||||
):
|
):
|
||||||
assert asset in html
|
assert asset in html
|
||||||
assert f'"{asset}"' in service_worker
|
assert f'"{asset}"' in service_worker
|
||||||
assert 'const CACHE_VERSION = "cognitive-agent-pad-v275"' in service_worker
|
assert 'const CACHE_VERSION = "cognitive-agent-pad-v276"' in service_worker
|
||||||
|
|
||||||
|
|
||||||
def test_vehicle_twin_icons_and_nested_overlays_use_semantic_tokens() -> None:
|
def test_vehicle_twin_icons_and_nested_overlays_use_semantic_tokens() -> None:
|
||||||
|
|||||||
@@ -248,3 +248,114 @@ def test_top_mode_action_formatter_uses_device_target_action_and_parameter_value
|
|||||||
"action": "设置空调温度",
|
"action": "设置空调温度",
|
||||||
"parameters": "目标温度:25 ℃",
|
"parameters": "目标温度:25 ℃",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_mode_cards_open_assistant_and_submit_enter_mode_command() -> None:
|
||||||
|
app = (WEB / "app.js").read_text(encoding="utf-8")
|
||||||
|
start = app.index("function applyModeCommand")
|
||||||
|
end = app.index("\n\nfunction applyRuntimeTargetPolicy", start)
|
||||||
|
snippet = app[start:end]
|
||||||
|
script = "const snippet = " + json.dumps(snippet) + ";\n" + r"""
|
||||||
|
const vm = require("node:vm");
|
||||||
|
const calls = [];
|
||||||
|
const input = {
|
||||||
|
value: "",
|
||||||
|
dataset: { speechStage: "final", speechUtteranceId: "old", speechValue: "old" },
|
||||||
|
dispatchEvent(event) { calls.push(["input", event.type, event.bubbles, this.value]); },
|
||||||
|
};
|
||||||
|
const form = { requestSubmit() { calls.push(["submit", input.value]); } };
|
||||||
|
const runButton = { disabled: false };
|
||||||
|
const savedMode = {
|
||||||
|
id: "reading-user",
|
||||||
|
name: "阅读模式",
|
||||||
|
command_flow: { source_request_id: "saved-request" },
|
||||||
|
};
|
||||||
|
const context = {
|
||||||
|
Event: class Event {
|
||||||
|
constructor(type, options = {}) { this.type = type; this.bubbles = options.bubbles; }
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
CognitiveAgentAssistant: {
|
||||||
|
isPending() { return false; },
|
||||||
|
open() { calls.push(["open"]); },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modeProfiles() { return [savedMode]; },
|
||||||
|
setModeRailSelection(mode) { calls.push(["select", mode?.id]); },
|
||||||
|
closeModeGallery(options) { calls.push(["close", options.restoreFocus]); },
|
||||||
|
showToast(message) { calls.push(["toast", message]); },
|
||||||
|
$(selector) {
|
||||||
|
return { "#utterance": input, "#requestForm": form, "#runButton": runButton }[selector];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
vm.createContext(context);
|
||||||
|
vm.runInContext(snippet + '\nthis.result = applyModeCommand(" 进入阅读模式 ", "reading-user");', context);
|
||||||
|
console.log(JSON.stringify({
|
||||||
|
result: context.result,
|
||||||
|
calls,
|
||||||
|
value: input.value,
|
||||||
|
speechDataset: input.dataset,
|
||||||
|
}));
|
||||||
|
"""
|
||||||
|
completed = subprocess.run(
|
||||||
|
[node_runtime(), "-e", script],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
result = json.loads(completed.stdout)
|
||||||
|
|
||||||
|
assert result["result"] is True
|
||||||
|
assert result["calls"] == [
|
||||||
|
["select", "reading-user"],
|
||||||
|
["close", False],
|
||||||
|
["open"],
|
||||||
|
["input", "input", True, "进入阅读模式"],
|
||||||
|
["submit", "进入阅读模式"],
|
||||||
|
]
|
||||||
|
assert result["value"] == "进入阅读模式"
|
||||||
|
assert result["speechDataset"] == {}
|
||||||
|
assert "executeSavedModeProfile" not in snippet
|
||||||
|
|
||||||
|
|
||||||
|
def test_mode_cards_do_not_submit_while_assistant_is_busy() -> None:
|
||||||
|
app = (WEB / "app.js").read_text(encoding="utf-8")
|
||||||
|
start = app.index("function applyModeCommand")
|
||||||
|
end = app.index("\n\nfunction applyRuntimeTargetPolicy", start)
|
||||||
|
snippet = app[start:end]
|
||||||
|
script = "const snippet = " + json.dumps(snippet) + ";\n" + r"""
|
||||||
|
const vm = require("node:vm");
|
||||||
|
const calls = [];
|
||||||
|
const context = {
|
||||||
|
window: { CognitiveAgentAssistant: { isPending() { return true; } } },
|
||||||
|
showToast(message) { calls.push(message); },
|
||||||
|
};
|
||||||
|
vm.createContext(context);
|
||||||
|
vm.runInContext(snippet + '\nthis.result = applyModeCommand("进入办公模式", "office");', context);
|
||||||
|
console.log(JSON.stringify({ result: context.result, calls }));
|
||||||
|
"""
|
||||||
|
completed = subprocess.run(
|
||||||
|
[node_runtime(), "-e", script],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
result = json.loads(completed.stdout)
|
||||||
|
|
||||||
|
assert result == {
|
||||||
|
"result": False,
|
||||||
|
"calls": ["AI助手正在处理上一条指令,请稍候"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_top_and_management_mode_cards_share_the_assistant_submission_entrypoint() -> None:
|
||||||
|
app = (WEB / "app.js").read_text(encoding="utf-8")
|
||||||
|
install_start = app.index('document.addEventListener("DOMContentLoaded"')
|
||||||
|
handlers = app[install_start:]
|
||||||
|
|
||||||
|
assert handlers.count("if (button) applyModeCommand(") == 2
|
||||||
|
assert '$(".mode-chips").addEventListener("click"' in handlers
|
||||||
|
assert '$("#modeGalleryGroups").addEventListener("click"' in handlers
|
||||||
|
assert app.count('button.dataset.modeCommand = `进入${mode.name}`;') == 2
|
||||||
|
|||||||
Reference in New Issue
Block a user