feat: 完善 N7 智能座舱交互与控制链路

This commit is contained in:
vee
2026-09-07 15:34:59 +08:00
parent 32d179143f
commit c96970f068
100 changed files with 30088 additions and 1174 deletions
@@ -0,0 +1,273 @@
from __future__ import annotations
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
def test_console_contains_ai_assistant_surface():
html = (ROOT / "apps/web-console/index.html").read_text(encoding="utf-8")
assert 'id="assistantTranscript"' in html
assert 'id="assistantMessages"' in html
assert "你好,我是 AI助手。你可以向我提问" in html
assert 'aria-label="AI助手对话"' in html
assert 'placeholder="向 AI助手提问或交代工作"' in html
assert 'aria-label="发送消息"' in html
assert 'href="/assistant-chat.css?v=codex-assistant-22"' in html
assert 'src="/assistant-chat.js?v=codex-assistant-20"' in html
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")
assert 'const CACHE_VERSION = "cognitive-agent-pad-v269"' in service_worker
assert '"/assistant-chat.css?v=codex-assistant-22"' in service_worker
assert '"/assistant-chat.js?v=codex-assistant-20"' in service_worker
def test_codex_frontend_uses_one_streaming_endpoint_and_agent_work_events():
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
assert '"/api/assistant/stream"' in script
assert 'Accept: "application/x-ndjson"' in script
assert '"action.started", "action.progress"' in script
assert 'event.type === "action.completed"' in script
assert 'event.type === "answer.delta"' in script
assert '"work.started", "work.progress"' in script
assert 'event.type === "work.completed"' in script
assert 'Object.prototype.hasOwnProperty.call(event, "answer")' in script
assert 'pendingController.abort("user_stop")' in script
assert 'signal.addEventListener("abort", cancelReader, { once: true })' in script
assert "reader.cancel(signal.reason)" in script
assert 'signal.removeEventListener("abort", cancelReader)' in script
assert "reader.releaseLock()" in script
assert 'if (!answerCompleted) throw new Error("助手响应意外中断,请重试。")' in script
assert "event.stopImmediatePropagation()" in script
assert "confirmation" not in script.lower()
def test_codex_request_keeps_trusted_vehicle_context_without_legacy_model_routing():
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
request_body = script.split("function requestBody(message)", 1)[1].split(
"async function readErrorResponse", 1
)[0]
assert 'client_type: "web_console_codex_assistant"' in request_body
for field in (
"execution_mode",
"control_transport",
"vehicle_id",
"hardware_client_id",
"simulation_fault",
):
assert field in request_body
assert "history" not in request_body
assert "provider" not in request_body
assert "reasoning_effort" not in request_body
def test_starting_a_new_codex_conversation_rotates_the_conversation_id():
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
assert 'const CONVERSATION_STORAGE_KEY = "cognitive-agent.conversation-id"' in script
assert "function createConversationId()" in script
assert "sessionStorage.setItem(CONVERSATION_STORAGE_KEY, createConversationId())" in script
def test_assistant_uses_the_shared_top_right_dock_and_opens_by_default():
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
assert 'trigger.id = "assistantDrawerToggle"' in script
assert 'drawer.id = "assistantDrawer"' in script
assert 'drawer.setAttribute("role", "dialog")' in script
assert 'drawer.setAttribute("aria-label", "AI助手")' in script
assert 'trigger.setAttribute("aria-controls", drawer.id)' in script
assert 'trigger.setAttribute("aria-pressed", "false")' in script
assert 'trigger.classList.toggle("active", open)' in script
assert 'trigger.setAttribute("aria-pressed", String(open))' in script
assert 'drawer.toggleAttribute("inert", !open)' in script
assert 'trigger.className = "assistant-drawer-toggle v2-panel-control-button"' in script
assert '($("#v2PanelControlDock") || document.body).append(trigger)' in script
assert 'id = "assistantDrawerClose"' not in script
assert "setDrawerOpen(true)" in script
assert 'event.key === "Escape"' in script
assert "#v2PanelControlDock > .assistant-drawer-toggle" in css
assert ".assistant-drawer-open .v2-operation-shell" in css
assert "position: fixed" in css
assert "position: relative" in css
assert "grid-template-rows: 56px auto minmax(0, 1fr)" in css
assert ".v2-output-shell" in css
def test_assistant_mode_creation_previews_then_saves_to_the_compact_mode_strip():
app = (ROOT / "apps/web-console/app.js").read_text(encoding="utf-8")
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
assert "function startModeCreation()" in assistant
assert 'input.value = "请帮我创建一个"' in assistant
assert "function appendModeDraftState(message, draft, actions)" in assistant
assert "function modeDraftRows(draft, rawActions)" in assistant
assert "event.payload.mode_profile_draft" in assistant
assert 'event.payload.orchestration_route === "mode_profile_create_preview"' in assistant
assert "appendModeDraftState(" in assistant
assert "保存为功能模式" in assistant
assert "先试运行" in assistant
save_flow = assistant.split(
"async function saveModeProfileDraft", 1
)[1].split("async function tryModeProfileDraft", 1)[0]
assert 'request("/api/mode-profiles"' in save_flow
assert 'method: "POST"' in save_flow
for field in ("name", "instruction", "source_request_id", "vehicle_id"):
assert field in save_flow
assert "response.ok" in save_flow
assert "refreshModeProfiles?.(payload.mode_profile?.id)" in save_flow
assert "appendModeCreatedState(message, payload.mode_profile)" in save_flow
assert "tryModeDraft?.(draft)" in assistant
assert "appendModeCreatedState(message, event.payload.mode_profile)" in assistant
assert "revealModeProfile?.(String(profile.id), { focus: true })" in assistant
assert "function revealModeProfile(modeProfileId" in app
assert "refreshModeProfiles" in app
assert "tryModeDraft" in app
assert 'button.classList.add("just-created")' in app
assert 'window.addEventListener("cognitive-agent:request-complete"' in app
assert ".assistant-mode-draft" in css
assert ".assistant-mode-draft-row" in css
assert ".assistant-mode-draft-save" in css
assert ".assistant-mode-draft-status.failed" in css
assert ".assistant-mode-created" in css
def test_assistant_composer_grows_to_a_cap_then_scrolls_its_own_content():
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
resize_flow = assistant.split(
"function resizeComposerInput", 1
)[1].split("function createMessage", 1)[0]
assert "const COMPOSER_MIN_HEIGHT = 64" in assistant
assert "const COMPOSER_MAX_HEIGHT = 116" in assistant
assert 'input.style.height = "auto"' in resize_flow
assert "input.scrollHeight" in resize_flow
assert "Math.min(contentHeight, COMPOSER_MAX_HEIGHT)" in resize_flow
assert 'input.style.height = `${nextHeight}px`' in resize_flow
assert 'input.classList.toggle("is-scrollable", scrollable)' in resize_flow
assert "if (!scrollable) input.scrollTop = 0" in resize_flow
assert 'input.addEventListener("input", () => resizeComposerInput(input))' in assistant
assert "resizeComposerInput(input)" in assistant
composer_styles = css.split(
".v2-request-form .command-composer", 1
)[1].split("}", 1)[0]
assert "height: auto" in composer_styles
input_styles = css.split(
".v2-request-form #utterance", 1
)[1].split("}", 1)[0]
assert "max-height: 116px" in input_styles
assert "overflow-y: hidden" in input_styles
scrollable_styles = css.split("#utterance.is-scrollable", 1)[1].split("}", 1)[0]
assert "overflow-y: auto" in scrollable_styles
assert "scrollbar-width: thin" in scrollable_styles
assert "overscroll-behavior: contain" in scrollable_styles
def test_assistant_composer_keeps_the_microphone_visible_and_aligned():
html = (ROOT / "apps/web-console/index.html").read_text(encoding="utf-8")
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
assert 'id="speechDictationButton"' in html
assert 'data-lucide="mic"' in html
assert ".v2-request-form .speech-dictation" in css
assert "right: 53px" in css
assert "bottom: auto" in css
assert "display: grid !important" in css
assert "transform: translateY(-50%)" in css
assert ".v2-request-form .speech-status:empty" in css
def test_voice_input_supports_direct_manual_capture_and_background_wake_standby():
html = (ROOT / "apps/web-console/index.html").read_text(encoding="utf-8")
script = (ROOT / "apps/web-console/speech-input.js").read_text(encoding="utf-8")
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
assert 'aria-label="开始语音输入"' in html
assert 'const DEFAULT_WAKE_WORD = "你好,双十一"' in script
assert 'new Set(["arming", "armed", "capturing", "finalizing"])' in script
assert "async function startManualDictation()" in script
assert "async function startWakeStandby({ automatic = true } = {})" in script
assert 'return startCapture("manual")' in script
assert 'return startCapture("wake", { automatic })' in script
assert 'else void startManualDictation()' in script
assert 'const manualInput = speech.inputMode === "manual"' in script
assert "const detectedWake = manualInput || wakeWord.detected === true" in script
assert 'fetch("/api/speech/preview"' in script
assert '"X-Speech-Preview-Sequence"' in script
assert '"X-Speech-Input-Mode": speech.inputMode' in script
assert 'result.stale === true' in script
assert 'sequence <= speech.lastAppliedPreviewSequence' in script
assert "wakeWord.remainder ?? result.live_text" in script
assert "status.wake_word.primary" in script
assert 'if (speech.wakeDetected && liveText)' in script
assert 'replaceInput(liveText, "partial")' in script
assert '"X-Speech-Input-Mode": inputMode' in script
assert '"X-Speech-Wake-Word-Required": inputMode === "wake" ? "1" : "0"' in script
assert (
"result.assistant_text || result.clean_text || result.live_text || "
"result.raw_text || result.text"
) in script
assert "commands.length" not in script
assert "input.dataset.speechExecutionEligible =" not in script
assert "void speech.setAssistantOpen(open)" in assistant
assert '["capturing", "finalizing"].includes(speechPhase)' in assistant
assert 'speechPhase === "arming" && speechInputMode === "manual"' in assistant
assert "请等待语音整理完成后再发送" in assistant
def test_voice_lifecycle_releases_hidden_or_collapsed_microphone():
script = (ROOT / "apps/web-console/speech-input.js").read_text(encoding="utf-8")
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
assert 'document.addEventListener("visibilitychange"' in script
assert "ACTIVE_PHASES.has(speech.phase)" in script
assert 'window.addEventListener("pagehide"' in script
assert "speech.stream?.getTracks().forEach((track) => track.stop())" in script
assert "speech.previewController?.abort" in script
assert "speech.finalController?.abort" in script
assert 'typeof speech?.setAssistantOpen === "function"' in assistant
assert "void speech.setAssistantOpen(open)" in assistant
def test_model_text_is_rendered_through_text_nodes_not_raw_html():
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
assert "document.createTextNode(part)" in script
assert "code.textContent =" in script
assert "contentNode.textContent = content" in script
assert "innerHTML = content" not in script
def test_chat_styles_include_accessible_responsive_and_reduced_motion_states():
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
assert ".assistant-message-user" in css
assert ".assistant-action-status.success" in css
assert "@media (max-width: 900px)" in css
assert "@media (max-width: 560px)" in css
assert ".v2-unified-right-rail" in css
assert "grid-template-columns: minmax(0, 1fr) !important" in css
assert "@media (prefers-reduced-motion: reduce)" in css
def test_assistant_does_not_restore_collapsed_sidebars_and_uses_mobile_overlays():
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
assert "--assistant-drawer-width: clamp(340px, 26vw, 410px);" in css
assert "@media (min-width: 901px)" in css
assert ".assistant-drawer-open.v2-sidebar-collapsed .workspace" in css
assert "grid-template-columns: 0 minmax(0, 1fr);" in css
assert ".assistant-drawer-open.v2-sidebar-collapsed .command-panel.v2-sidebar" in css
assert "visibility: hidden;" in css
assert "grid-template-columns: var(--assistant-drawer-width) minmax(0, 1fr);" in css
assert "#v2PanelControlDock > .assistant-drawer-toggle" in css
assert "opacity: 1 !important;" in css
assert "pointer-events: auto !important;" in css
assert "@media (max-width: 900px)" in css
assert "width: min(86vw, 320px);" in css
assert "width: min(86vw, 360px);" in css
assert "top: 60px;" in css
assert 'event.key === "Escape"' in script