feat: 完善 N7 智能座舱交互与控制链路
This commit is contained in:
@@ -635,9 +635,12 @@ def test_left_sidebar_extracts_speech_and_peripheral_control_commands() -> None:
|
||||
}
|
||||
|
||||
|
||||
def test_interactive_console_keeps_actions_inside_composer_and_collapsed_rails_aligned() -> None:
|
||||
def test_interactive_console_uses_one_top_right_icon_dock_for_all_five_panels() -> None:
|
||||
console = source("console-v2.js")
|
||||
assistant = source("assistant-chat.js")
|
||||
energy = source("energy-monitor.js")
|
||||
styles = source("console-v2.css")
|
||||
extension = source("self-extension.js")
|
||||
|
||||
assert 'const resetButton = $("#newConversation", originalForm);' in console
|
||||
assert 'const runButton = $("#runButton", originalForm);' in console
|
||||
@@ -647,10 +650,38 @@ def test_interactive_console_keeps_actions_inside_composer_and_collapsed_rails_a
|
||||
assert ".v2-request-form .command-composer" in styles
|
||||
assert "overflow: hidden;" in styles
|
||||
assert "transform: translateY(-50%);" in styles
|
||||
assert '.joint-twin-shell[data-open-panels="none"]' in styles
|
||||
assert "grid-template-columns: 48px minmax(520px, 1fr) 48px;" in styles
|
||||
assert "writing-mode: horizontal-tb;" in styles
|
||||
assert "writing-mode: vertical-rl;" in styles
|
||||
assert console.index('id="v2DevicePanel"') < console.index('id="v2ExtensionPanel"')
|
||||
assert 'class="v2-side-status-stack"' in console
|
||||
assert 'class="v2-panel-control-dock"' in console
|
||||
assert 'panelDock.append(sidebarToggle)' in console
|
||||
assert 'panelDock.append(deviceToggle, extensionToggle)' in console
|
||||
assert 'class="v2-side-panel-dock"' not in console
|
||||
assert 'deviceToggle.dataset.panelToggle = "devices"' in console
|
||||
assert 'extensionToggle.dataset.panelToggle = "extensions"' in console
|
||||
assert 'data-lucide="car-front"' in console
|
||||
assert 'data-lucide="usb"' in console
|
||||
assert 'data-lucide="bot-message-square"' in assistant
|
||||
assert '($("#v2PanelControlDock") || document.body).append(trigger)' in assistant
|
||||
assert 'trigger.className = "assistant-drawer-toggle v2-panel-control-button"' in assistant
|
||||
assert 'trigger.className = "energy-monitor-toggle v2-panel-control-button"' in energy
|
||||
assert '$("#v2PanelControlDock").append(trigger)' in energy
|
||||
assert 'trigger.setAttribute("aria-controls", "energyMonitorLayer")' in energy
|
||||
assert "panel.hidden = !visible;" in console
|
||||
assert 'rightRail.hidden = collapsed;' in console
|
||||
assert 'rightRail.toggleAttribute("inert", collapsed)' in console
|
||||
assert "data-panel-toggle" not in extension
|
||||
assert 'setSidePanelVisibility("devices", false)' in console
|
||||
assert 'setSidePanelVisibility("extensions", false)' in console
|
||||
assert "setDrawerOpen(true)" in assistant
|
||||
assert ".v2-panel-control-dock" in styles
|
||||
assert "#v2PanelControlDock > .v2-panel-control-button" in styles
|
||||
assert '.v2-panel-control-button[aria-pressed="true"]' in styles
|
||||
assert "background: var(--primary);" in styles
|
||||
assert "width: 38px !important;" in styles
|
||||
final_styles = styles.split("/* AI assistant light-blue final override", 1)[1]
|
||||
assert 'data-open-panels="none"' in final_styles
|
||||
assert "grid-template-columns: minmax(0, 1fr);" in final_styles
|
||||
assert ".v2-unified-right-rail[hidden]" in final_styles
|
||||
|
||||
|
||||
def test_left_sync_rail_can_collapse_and_log_aligns_with_lower_workspace() -> None:
|
||||
@@ -660,8 +691,14 @@ def test_left_sync_rail_can_collapse_and_log_aligns_with_lower_workspace() -> No
|
||||
assert 'make("section", "v2-sync-sidebar"' in console
|
||||
assert '<div class="v2-sync-sidebar-overview">' in console
|
||||
assert "syncOverview?.append(sidebarFooter);" in console
|
||||
assert '$(".v2-sync-sidebar-heading", syncSidebar)?.append(sidebarToggle);' in console
|
||||
assert '$(".v2-sync-sidebar-heading", syncSidebar)?.append(sidebarToggle);' not in console
|
||||
assert 'commandPanel.id = "v2SidebarPanel"' in console
|
||||
assert 'sidebarToggle.setAttribute("aria-controls", commandPanel.id)' in console
|
||||
assert "sidebarCollapsed: true" in console
|
||||
assert 'sidebar.toggleAttribute("inert", ui.sidebarCollapsed)' in console
|
||||
assert 'button.setAttribute("aria-pressed", String(expanded))' in console
|
||||
assert "function syncSidebarLogBaseline()" in console
|
||||
assert "if (!sidebar || !lowerPanel || ui.sidebarCollapsed) return;" in console
|
||||
assert "const requiredOverviewHeight = overviewRect" in console
|
||||
assert "overviewBottom - overviewRect.top + overviewPaddingBottom + 10" in console
|
||||
assert 'sidebar.style.setProperty("--v2-sync-log-top"' in console
|
||||
@@ -676,8 +713,10 @@ def test_left_sync_rail_can_collapse_and_log_aligns_with_lower_workspace() -> No
|
||||
assert "align-self: start;" in styles
|
||||
assert "overflow: visible;" in styles
|
||||
assert ".v2-sync-sidebar > .v2-sync-log" in styles
|
||||
assert "grid-template-columns: 64px minmax(0, 1fr);" in styles
|
||||
assert ".v2-sidebar-collapsed #v2SidebarToggle" in styles
|
||||
final_styles = styles.split("/* AI assistant light-blue final override", 1)[1]
|
||||
assert "grid-template-columns: 0 minmax(0, 1fr);" in final_styles
|
||||
assert ".command-panel.v2-sidebar" in final_styles
|
||||
assert "visibility: hidden;" in final_styles
|
||||
|
||||
|
||||
def test_twin_device_status_popover_has_clear_close_paths() -> None:
|
||||
@@ -696,6 +735,7 @@ def test_twin_device_status_popover_has_clear_close_paths() -> None:
|
||||
|
||||
|
||||
def test_twin_device_hotspots_use_responsive_hover_and_leader_labels() -> None:
|
||||
app = source("app.js")
|
||||
console = source("console-v2.js")
|
||||
styles = source("console-v2.css")
|
||||
|
||||
@@ -713,12 +753,61 @@ def test_twin_device_hotspots_use_responsive_hover_and_leader_labels() -> None:
|
||||
assert ".v2-twin-hotspots button::before" in styles
|
||||
assert ".v2-twin-hotspots button::after" in styles
|
||||
assert ".v2-twin-hotspot-label" in styles
|
||||
assert "--v2-hotspot-label-x: -115px" in styles
|
||||
assert "--v2-hotspot-line-angle: -150deg" in styles
|
||||
assert "width: 94px;" in styles
|
||||
assert "max-width: 94px;" in styles
|
||||
assert "box-sizing: border-box;" in styles
|
||||
assert "text-overflow: ellipsis;" in styles
|
||||
assert "function twinDeviceWorldAnchor(" in app
|
||||
assert "function positionTwinDeviceHotspots()" in app
|
||||
assert "projectedHotspot.copy(hotspotWorldAnchor).project(camera)" in app
|
||||
assert 'button.dataset.anchorSource = "projected-3d"' in app
|
||||
assert 'hotspotLayer.dataset.anchorLayout = "projected-3d"' in app
|
||||
assert "clampLabelCoordinate(" in app
|
||||
assert "positionTwinDeviceHotspots();" in app
|
||||
assert "function positionTwinDevicePopover(" in console
|
||||
assert "function twinPopoverIntersectionArea(" in console
|
||||
assert 'Object.keys(availableSpace).sort' in console
|
||||
assert 'popover.dataset.anchorDevice = deviceId' in console
|
||||
assert 'popover.dataset.placement = best.placement' in console
|
||||
assert 'popover.dataset.shadeOverlap = String(Math.round(best.shadeOverlap))' in console
|
||||
assert 'popover.dataset.labelOverlap = String(Math.round(best.labelOverlap))' in console
|
||||
assert 'popover.dataset.layoutSignature === geometrySignature' in console
|
||||
assert 'window.CognitiveAgentConsoleV2?.positionTwinDevicePopover?.();' in app
|
||||
popover_styles = styles.split(".console-v2 .v2-twin-device-popover {", 1)[1].split("}", 1)[0]
|
||||
assert "top: 52px;" not in popover_styles
|
||||
assert "left: 50%;" not in popover_styles
|
||||
assert "transform: translateX(-50%);" not in popover_styles
|
||||
assert 'width: min(280px, calc(100% - 24px));' in styles
|
||||
assert 'repeat(auto-fit, minmax(44px, 1fr))' in styles
|
||||
for device_id in (
|
||||
"windows", "shade", "driver", "passenger",
|
||||
"reading", "ambient", "headlight", "climate",
|
||||
):
|
||||
assert f"{device_id}: {{ normalized:" in app
|
||||
assert '.v2-twin-device-popover[data-pinned="false"]' in styles
|
||||
assert "pointer-events: none;" in styles
|
||||
|
||||
|
||||
def test_vehicle_twin_keeps_whole_car_centered_and_removes_axis_callouts() -> None:
|
||||
html = source("index.html")
|
||||
app = source("app.js")
|
||||
styles = source("console-v2.css")
|
||||
|
||||
assert 'twin.view.targetX = 0;' in app
|
||||
assert 'twin.view.yaw = -0.62;' in app
|
||||
assert 'canvas.dataset.framing = "whole-vehicle-centered"' in app
|
||||
assert "1.28 / Math.max(0.64, camera.aspect)" in app
|
||||
assert 'class="twin-callout-layer"' not in html
|
||||
assert 'class="twin-callout' not in html
|
||||
assert "data-callout=" not in html
|
||||
assert "function positionTwinCallouts()" not in app
|
||||
assert "renderTwinCalloutValues" not in app
|
||||
assert ".joint-twin-shell > .twin-band" in styles
|
||||
assert "container-type: inline-size;" in styles
|
||||
assert "@container (max-width: 420px)" in styles
|
||||
assert "grid-template-columns: minmax(0, 1fr);" in styles
|
||||
|
||||
|
||||
def test_validated_can_commands_survive_same_task_running_poll_and_keep_error_detail() -> None:
|
||||
console = source("console-v2.js")
|
||||
app = source("app.js")
|
||||
@@ -733,3 +822,118 @@ def test_validated_can_commands_survive_same_task_running_poll_and_keep_error_de
|
||||
assert "failed_or_blocked: \"失败或已阻止\"" in console
|
||||
assert "requestError.outcome = phaseEvent.outcome;" in app
|
||||
assert "requestError.detail = phaseEvent.detail;" in app
|
||||
|
||||
|
||||
def test_compact_mode_strip_supports_wheel_drag_and_keyboard_navigation() -> None:
|
||||
app = source("app.js")
|
||||
styles = source("console-v2.css")
|
||||
|
||||
scroller = app.split("function installModeChipScroller()", 1)[1].split(
|
||||
"function populateModeChips", 1
|
||||
)[0]
|
||||
assert 'group.dataset.modeScrollerBound = "true"' in scroller
|
||||
assert "group.tabIndex = 0" in scroller
|
||||
assert "滚轮、拖动或方向键横向浏览" in scroller
|
||||
assert 'group.addEventListener("wheel"' in scroller
|
||||
assert "Math.abs(event.deltaX) > Math.abs(event.deltaY)" in scroller
|
||||
assert "scrollBy(dominantDelta * multiplier)" in scroller
|
||||
assert "event.preventDefault()" in scroller
|
||||
assert "{ passive: false }" in scroller
|
||||
|
||||
assert 'group.addEventListener("pointerdown"' in scroller
|
||||
assert 'event.pointerType !== "mouse"' in scroller
|
||||
assert "group.setPointerCapture?.(pointerId)" in scroller
|
||||
assert 'group.addEventListener("pointermove"' in scroller
|
||||
assert "Math.abs(distance) < 5" in scroller
|
||||
assert 'group.classList.add("is-dragging")' in scroller
|
||||
assert 'group.addEventListener("pointerup", finishPointerDrag)' in scroller
|
||||
assert 'group.addEventListener("pointercancel", finishPointerDrag)' in scroller
|
||||
assert "event.stopImmediatePropagation()" in scroller
|
||||
|
||||
assert 'group.addEventListener("keydown"' in scroller
|
||||
for key in ("ArrowLeft", "ArrowRight", "PageUp", "PageDown", "Home", "End"):
|
||||
assert f'event.key === "{key}"' in scroller
|
||||
assert "new MutationObserver" in scroller
|
||||
assert 'new ResizeObserver(updateScrollState).observe(group)' in scroller
|
||||
assert "installModeChipScroller();" in app
|
||||
|
||||
mode_strip_styles = styles.split("/* Compact function-mode strip", 1)[1]
|
||||
assert "overflow-x: auto" in mode_strip_styles
|
||||
assert "overflow-y: hidden" in mode_strip_styles
|
||||
assert "touch-action: pan-x" in mode_strip_styles
|
||||
assert "cursor: grab" in mode_strip_styles
|
||||
assert ".mode-chips:focus-visible" in mode_strip_styles
|
||||
assert ".mode-chips.is-dragging" in mode_strip_styles
|
||||
assert "cursor: grabbing" in mode_strip_styles
|
||||
|
||||
|
||||
def test_assistant_mode_preview_groups_validated_actions_for_the_card() -> None:
|
||||
node = shutil.which("node")
|
||||
if node is None:
|
||||
return
|
||||
assistant = source("assistant-chat.js")
|
||||
helper_start = assistant.index(" function modeDraftRows")
|
||||
helper_end = assistant.index("\n\n async function saveModeProfileDraft", helper_start)
|
||||
helper = assistant[helper_start:helper_end]
|
||||
draft = {
|
||||
"name": "观影模式",
|
||||
"instruction": "遮阳帘关闭,车窗关闭,副驾座椅调整为零重力模式,灯光全部关闭",
|
||||
}
|
||||
actions = [
|
||||
{
|
||||
"domain": "roof",
|
||||
"capability": "roof.set_shade_motion",
|
||||
"target": {"zone": "vehicle"},
|
||||
"parameters": {"motion": "close"},
|
||||
},
|
||||
{
|
||||
"domain": "window",
|
||||
"capability": "window.set_motion",
|
||||
"target": {"zone": "all"},
|
||||
"parameters": {"motion": "close"},
|
||||
},
|
||||
{
|
||||
"domain": "seat",
|
||||
"capability": "seat.set_longitudinal_position",
|
||||
"target": {"zone": "front_right", "instance": "passenger"},
|
||||
"parameters": {"position_pct": 100},
|
||||
},
|
||||
{
|
||||
"domain": "seat",
|
||||
"capability": "seat.set_backrest_recline",
|
||||
"target": {"zone": "front_right", "instance": "passenger"},
|
||||
"parameters": {"position_pct": 81, "angle_deg": 150},
|
||||
},
|
||||
{
|
||||
"domain": "interior_light",
|
||||
"capability": "interior_light.set_indoor_power",
|
||||
"target": {"zone": "vehicle"},
|
||||
"parameters": {"state": "off"},
|
||||
},
|
||||
{
|
||||
"domain": "ambient_light",
|
||||
"capability": "ambient_light.set_power",
|
||||
"target": {"zone": "vehicle"},
|
||||
"parameters": {"state": "off"},
|
||||
},
|
||||
]
|
||||
script = (
|
||||
f"{helper}\n"
|
||||
f"const draft = {json.dumps(draft, ensure_ascii=False)};\n"
|
||||
f"const actions = {json.dumps(actions, ensure_ascii=False)};\n"
|
||||
"process.stdout.write(JSON.stringify(modeDraftRows(draft, actions)));"
|
||||
)
|
||||
completed = subprocess.run(
|
||||
[node, "-e", script],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
assert json.loads(completed.stdout) == [
|
||||
{"label": "遮阳帘", "value": "关闭", "icon": "blinds", "order": 0},
|
||||
{"label": "全部车窗", "value": "关闭", "icon": "panels-top-left", "order": 6},
|
||||
{"label": "副驾座椅", "value": "零重力模式", "icon": "armchair", "order": 11},
|
||||
{"label": "座舱灯光", "value": "全部关闭", "icon": "lightbulb-off", "order": 24},
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user