(function () { "use strict"; const pageDefinitions = [ { index: 0, label: "整车联合数字孪生", shortLabel: "数字孪生", target: ".joint-twin-shell" }, { index: 1, label: "决策轨迹与校验", shortLabel: "决策校验", target: "#panel-trace" }, { index: 2, label: "指令配置与硬件映射", shortLabel: "配置映射", target: "#panel-config" }, { index: 3, label: "CAN 编译计划", shortLabel: "CAN 计划", target: "#panel-plan" }, { index: 4, label: "执行结果与硬件回读", shortLabel: "执行回读", target: "#panel-result" }, ]; const $ = (selector, root = document) => root.querySelector(selector); const $$ = (selector, root = document) => [...root.querySelectorAll(selector)]; const ui = { controlMode: sessionStorage.getItem("cognitive-agent.control-mode") === "gesture" ? "gesture" : "natural", activeModule: "self-orchestration", activePage: 0, sidebarCollapsed: true, }; 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: ids[0] || "", utterance: String(result.utterance || "").trim(), }; } function validatedCommandsBelongToTask(task = {}) { if (!validatedCommandsVisible) return false; const result = task.result && typeof task.result === "object" ? task.result : {}; const requestIds = commandCorrelationIds(result); const utterance = String(task.utterance || result.utterance || "").trim(); if (validatedCommandContext.requestId && requestIds.length && !requestIds.includes(validatedCommandContext.requestId)) return false; if (validatedCommandContext.utterance && utterance && validatedCommandContext.utterance !== utterance && !validatedCommandContext.utterance.includes(utterance) && !utterance.includes(validatedCommandContext.utterance)) return false; return true; } function make(tag, className, html) { const node = document.createElement(tag); if (className) node.className = className; if (html != null) node.innerHTML = html; return node; } function escapeHtml(value) { return String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } const normalizedCanTarget = (target = {}) => String( target.zone || target.instance || target.device || "vehicle", ).replaceAll("_", "-"); const normalizedOnOff = (value) => { if ([true, 1, "1", "on", "open", "enabled"].includes(value)) return "on"; if ([false, 0, "0", "off", "close", "disabled"].includes(value)) return "off"; return null; }; const boundedInteger = (value, minimum, maximum) => { const numeric = Number(value); return Number.isInteger(numeric) && numeric >= minimum && numeric <= maximum ? numeric : 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 || {}; const target = normalizedCanTarget(operation.target); const onOff = normalizedOnOff(parameters.state ?? parameters.power); const hyphenated = (value) => String(value || "").replaceAll("_", "-"); const parseSource = String(operation.parse_source || ""); const command = (category, text, channel = "CAN1", frame = "") => ({ channel, category, command: text, ...(frame ? { frame } : {}), ...(parseSource ? { parseSource } : {}), }); const hexByte = (value) => Number(value).toString(16).padStart(2, "0").toUpperCase(); const littleEndian16 = (value) => { const numeric = Number(value) & 0xFFFF; return `${hexByte(numeric & 0xFF)} ${hexByte((numeric >>> 8) & 0xFF)}`; }; const nativeCan0Frame = (opcode, motor, direction, paramA, paramB) => { const control = (Number(motor) & 0x03) | ((Number(direction) & 0x03) << 2) | 0x10; return `0x300 · ${hexByte(opcode)} ${hexByte(control)} ${littleEndian16(paramA)} ${littleEndian16(paramB)}`; }; if (capability === "infrared.emit") { const slot = boundedInteger(parameters.slot, 1, 248); const action = String(parameters.action || "红外动作").trim(); const deviceName = String(operation.target?.name || operation.target?.device || "红外外设").trim(); if (slot !== null) { return { channel: "IR", category: `${deviceName} · ${action}`, command: `infrared emit ${slot}`, frame: JSON.stringify({ action: "emit", data: { no: slot }, type: "infrared" }), frameLabel: "GSCU1B TCP", ...(parseSource ? { parseSource } : {}), }; } } const isPassengerSeat = new Set(["passenger", "front-right"]).has(target); if (isPassengerSeat) { const passengerAxes = { "seat.set_longitudinal_position": ["副驾座椅前后调节", "horizontal", 0], "seat.set_cushion_tilt": ["副驾座椅高度调节", "basin", 1], "seat.set_backrest_recline": ["副驾座椅靠背调节", "backrest", 2], "seat.set_legrest_height": ["副驾座椅腿托调节", "legrest", 3], }; const axis = passengerAxes[capability]; const position = boundedInteger(parameters.position_pct, 0, 100); if (axis && position !== null) { const motion = operation.motion || {}; const delta = boundedInteger(motion.delta_position_pct, 1, 100); const relativeDirections = { horizontal: { increase: "backward", decrease: "forward" }, basin: { increase: "down", decrease: "up" }, backrest: { increase: "backward", decrease: "forward" }, legrest: { increase: "up", decrease: "down" }, }; const relativeDirection = relativeDirections[axis[1]]?.[motion.direction]; return command( axis[0], motion.mode === "relative" && relativeDirection && delta !== null ? `seat passenger ${axis[1]} relative ${relativeDirection} ${delta}` : `seat passenger ${axis[1]} position ${position}`, "CAN0", position === 0 ? nativeCan0Frame(0x0B, axis[2], 0, 0x4B52, 120) : nativeCan0Frame(0x01, axis[2], 0, position * 100, 30000), ); } if (new Set(["seat.set_ventilation", "seat.set_heating"]).has(capability)) { const feature = capability === "seat.set_heating" ? "heat" : "vent"; const level = boundedInteger(parameters.level, 1, 3); const setting = onOff === "off" ? "off" : ({ 1: "low", 2: "medium", 3: "high" })[level]; if (setting) { return command( "副驾座椅通风/加热", `seat passenger ${feature} ${setting}`, "CAN0", ); } } if (capability === "seat.start_dynamic_mode") { const mode = boundedInteger(parameters.mode_id, 1, 2); if (mode !== null) { const dynamic = mode === 1 ? "rocking-horse" : "cradle"; return command( "副驾座椅动态模式", `seat passenger dynamic ${dynamic}`, "CAN0", nativeCan0Frame(0x0E, 0, 0, mode, 0), ); } } if (capability === "seat.stop_dynamic_mode") { return command( "副驾座椅动态模式", "seat passenger dynamic stop", "CAN0", nativeCan0Frame(0x0F, 0, 0, 0, 0), ); } } if (capability === "door.set_rear_lock") { const lockState = String(parameters.state || ""); const action = new Set(["lock", "unlock"]).has(lockState) ? lockState : onOff === "on" ? "lock" : onOff === "off" ? "unlock" : null; if (action) return command("后排门锁", `door rear ${action}`); } if (capability === "door.set_vehicle_lock") { const lockState = String(parameters.state || ""); if (new Set(["lock", "unlock"]).has(lockState)) { return command("整车门锁", `door vehicle ${lockState}`); } } if (capability === "window.set_motion") { const scope = new Set(["all", "front", "rear", "front-left", "front-right", "rear-left", "rear-right"]); const motion = String(parameters.motion || parameters.action || ""); if (scope.has(target) && new Set(["open", "close", "stop"]).has(motion)) { return command("车窗动作", `window ${target} ${motion}`); } } if (capability === "window.set_position") { const position = boundedInteger(parameters.position_pct, 0, 100); if (new Set(["front-left", "front-right", "rear-left", "rear-right"]).has(target) && position !== null) { return command("单窗位置", `window ${target} position ${position}`); } } if (capability === "climate.set_power" && onOff) { return command("空调电源", `ac ${onOff}`); } if (capability === "climate.set_fan_level") { const level = boundedInteger(parameters.level ?? parameters.fan_level, 1, 7); if (level !== null) return command("空调风量", `ac fan ${level}`); } if (capability === "climate.set_outlet_mode") { const mode = hyphenated(parameters.mode ?? parameters.outlet_mode); const modes = new Set(["face", "face-feet", "feet", "feet-defrost", "defrost", "face-feet-defrost", "face-defrost"]); if (modes.has(mode)) return command("出风模式", `ac mode ${mode}`); } if (capability === "climate.set_temperature") { const temperature = boundedInteger(parameters.temperature_c, 18, 32); if (new Set(["driver", "passenger", "rear"]).has(target) && temperature !== null) { return command("空调温度", `ac temp ${target} ${temperature}`); } const vehicleZones = new Set([ "vehicle", "front-row", "rear-row", "rear-left", "rear-right", ]); if (vehicleZones.has(target) && temperature !== null) { return command( "空调温度", `Climate_Control_064_04(${target}=${temperature}°C)`, ); } } if (capability === "climate.set_control_mode") { const mode = String(parameters.mode ?? parameters.control_mode ?? ""); if (new Set(["manual", "auto"]).has(mode)) return command("空调控制方式", `ac control ${mode}`); } if (capability === "climate.set_cycle_mode") { const mode = String(parameters.mode ?? parameters.cycle_mode ?? ""); if (new Set(["internal", "external"]).has(mode)) return command("空调循环", `ac cycle ${mode}`); } if (capability === "outlet.set_switch_state") { const state = String(parameters.state ?? ""); if (new Set(["driver-left", "driver-right", "passenger-left", "passenger-right"]).has(target) && new Set(["open", "close"]).has(state)) { return command("单风口开关", `outlet ${target} ${state}`); } } if (capability === "outlet.set_position_percent") { const horizontal = boundedInteger(parameters.horizontal_pct, 0, 100); const vertical = boundedInteger(parameters.vertical_pct, 0, 100); if (new Set(["driver-left", "driver-right", "passenger-left", "passenger-right"]).has(target) && horizontal !== null && vertical !== null) { return command("风口百分比", `outlet ${target} percent ${horizontal} ${vertical}`); } } if (capability === "outlet.set_calibrated_position") { const horizontal = boundedInteger(parameters.horizontal_value, -128, 126); const vertical = boundedInteger(parameters.vertical_value, -128, 126); if (new Set(["driver-left", "driver-right", "passenger-left", "passenger-right"]).has(target) && horizontal !== null && vertical !== null) { return command("风口标定坐标", `outlet ${target} position ${horizontal} ${vertical}`); } } if (new Set(["seat.set_driver_ventilation_native", "seat.set_driver_heating_native"]).has(capability) && new Set(["driver", "front-left"]).has(target)) { const feature = capability.includes("heating") ? "heat" : "vent"; const level = boundedInteger(parameters.level, 1, 2); const setting = onOff === "off" ? "off" : level === 1 ? "high" : level === 2 ? "low" : null; if (setting) return command("主驾座椅通风/加热", `seat driver ${feature} ${setting}`); } if (capability === "seat.set_driver_massage_native" && new Set(["driver", "front-left"]).has(target)) { const action = String(parameters.action || ""); if (action === "off") return command("主驾座椅按摩", "seat driver massage off"); if (action === "strength") { const strength = { 1: "weak", 2: "medium", 3: "strong" }[Number(parameters.strength)] || (new Set(["weak", "medium", "strong"]).has(parameters.strength) ? parameters.strength : null); if (strength) return command("主驾座椅按摩", `seat driver massage strength ${strength}`); } if (action === "mode" && new Set(["wave", "pulse", "stretch", "slack", "relax"]).has(parameters.mode)) { return command("主驾座椅按摩", `seat driver massage mode ${parameters.mode}`); } } if (capability === "seat.set_driver_axis_native" && new Set(["driver", "front-left"]).has(target)) { const axis = String(parameters.axis || ""); const direction = String(parameters.direction || ""); const validDirections = { horizontal: new Set(["forward", "backward", "stop"]), height: new Set(["up", "down", "stop"]), backrest: new Set(["forward", "backward", "stop"]), basin: new Set(["up", "down", "stop"]), }; if (validDirections[axis]?.has(direction)) { if (direction === "stop") return command("主驾座椅调节", `seat driver ${axis} stop`); const duration = boundedInteger(parameters.duration_ms, 100, 2000); if (duration !== null) return command("主驾座椅调节", `seat driver ${axis} ${direction} ${duration}`); } } if (capability === "seat.set_driver_position_native") { const position = boundedInteger(parameters.position ?? parameters.slot, 1, 3); if (position !== null) return command("主驾座椅记忆", `seat driver position ${position}`); } if (capability === "seat.recall_driver_memory_native") { const position = boundedInteger(parameters.position ?? parameters.slot, 1, 3); if (position !== null) return command("主驾座椅记忆", `seat driver memory recall ${position}`); } if (capability === "interior_light.set_indoor_power" && onOff) { return command("室内灯总开关", `light indoor ${onOff}`); } if (capability === "interior_light.set_reading_power" && target === "all" && onOff) { return command("全部阅读灯", `light reading all ${onOff}`); } if (capability === "ambient_light.set_power" && onOff) { return command("氛围灯", `light ambient ${onOff}`); } if (capability === "ambient_light.set_color_index") { const color = boundedInteger(parameters.color_index, 1, 127); if (color !== null) return command("氛围灯", `light ambient color ${color}`); } if (capability === "ambient_light.set_brightness_level") { const level = boundedInteger(parameters.brightness_level, 1, 5); if (level !== null) return command("氛围灯", `light ambient brightness ${level}`); } if (capability === "ambient_light.set_illumination") { const color = String(parameters.color_hex || "").trim().toUpperCase(); const brightness = boundedInteger(parameters.brightness_pct, 0, 100); if (/^#[0-9A-F]{6}$/.test(color) && brightness !== null) { return command( "氛围灯颜色与亮度", `Ambient_RGB_Control_32B_2B(color=${color}, brightness=${brightness}%)`, ); } } if (capability === "ambient_light.set_mode") { const mode = String(parameters.mode || "").trim(); if (new Set(["camping", "rest", "cinema", "meditation", "romantic", "off", "on"]).has(mode)) { return command("氛围灯模式", `Ambient_Mode_Control_496_04(mode=${mode})`); } } if (capability === "roof.set_shade_motion") { const motion = String(parameters.motion || ""); if (new Set(["open", "close", "half", "stop"]).has(motion)) { return command("遮阳帘", `shade ${motion}`); } } if (capability === "roof.set_shade_position") { const position = boundedInteger(parameters.position_pct ?? parameters.shade_position_pct, 0, 100); if (position !== null) return command("遮阳帘", `shade position ${position}`); } return null; } function parsedCanCommands(result = {}) { const configurations = result.function_configs || {}; const configurationEntries = Object.entries(configurations); if (!configurationEntries.length && result.function_config) { configurationEntries.push(["default", result.function_config]); } const executionResults = [ ...(result.execution_report?.spec?.results || []), ...(result.operation_results || []), ...Object.values(result.phase_execution_reports || {}).flatMap( (report) => report?.spec?.results || [], ), ]; const executionByRef = new Map(); executionResults.forEach((execution) => { const ref = String(execution?.ref || ""); if (!ref) return; const previous = executionByRef.get(ref); if (!previous || (!previous.telemetry && execution?.telemetry)) { executionByRef.set(ref, execution); } }); const submittedStates = new Set(["submitted", "queued", "published", "sent", "delivered"]); const deliveryForOperation = (operation) => { const capability = String(operation?.capability || ""); const operationId = String(operation?.id || ""); const execution = executionByRef.get(operationId) || executionResults.find((candidate) => { const ref = String(candidate?.ref || ""); return ref && operationId && ( operationId.startsWith(`${ref}-`) || ref.startsWith(`${operationId}-`) ); }); const telemetry = execution?.telemetry || {}; const mqttMessages = Array.isArray(telemetry.mqtt_messages) ? telemetry.mqtt_messages : []; const deliveryStates = [ telemetry.delivery_status, ...mqttMessages.map((message) => message?.delivery_status), ].map((value) => String(value || "").toLowerCase()); const submitted = result.execution_mode === "hardware" && telemetry.simulated !== true && ( deliveryStates.some((value) => submittedStates.has(value)) || (Array.isArray(telemetry.sent_frames) && telemetry.sent_frames.length > 0) || telemetry.gateway_acknowledged === true || telemetry.feedback_observed === true || telemetry.physical_effect_confirmed === true ); const failureMessage = String(execution?.message || "").trim(); const executionFailed = execution?.status === "failed" || telemetry.verification_status === "failed_or_blocked"; const safetyBlocked = executionFailed && !submitted && ( /no fresh valid 0x41a shade position/i.test(failureMessage) || /refusing (?:blind )?motion/i.test(failureMessage) || /thermal protection/i.test(failureMessage) ); const deliveryState = safetyBlocked ? "blocked" : executionFailed ? "failed" : submitted ? "submitted" : "not_submitted"; const deliveryLabel = safetyBlocked ? "安全阻止" : executionFailed ? "执行失败" : submitted ? "下发" : "未下发"; const deliveryDetail = /no fresh valid 0x41a shade position/i.test(failureMessage) ? "未收到遮阳帘 CAN1 / 0x41A 实时位置,无法判断移动方向,系统已阻止盲动" : failureMessage; const transportLabel = capability === "infrared.emit" ? "红外 TCP" : result.control_transport === "wireless_mqtt" ? "无线 CAN" : "有线 CAN"; return { deliveryState, deliveryLabel, deliveryTransport: transportLabel, ...(deliveryDetail ? { deliveryDetail } : {}), }; }; const commands = []; 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.operationRef || item.semantic}:${item.channel}:${item.command}`; const previous = unique.get(key); if (!previous || (previous.deliveryState !== "submitted" && item.deliveryState === "submitted") || (previous.parseSource !== "local_rule" && item.parseSource === "local_rule")) { unique.set(key, item); } }); return [...unique.values()]; } function parsedCommandSourceLabel(source) { return { direct_control: "设备控件", saved_mode_command_flow: "模式卡片", local_rule: "本地规则", speech_semantic: "语音语义", }[source] || "云端模型"; } 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 = `

阶段耗时计时中 · ${commandTimingState.inputMode === "speech" ? "语音输入" : "普通文本"}

`; return; } const total = [...rows].reverse().find((row) => row.group === "total"); container.innerHTML = `
阶段耗时 ${commandTimingState.inputMode === "speech" ? "语音输入" : "普通文本"} ${total ? `${escapeHtml(formattedDuration(total.durationMs))}` : ""}
    ${rows.map((row) => `
  1. ${escapeHtml(row.label)}${escapeHtml(formattedDuration(row.durationMs))}
  2. `).join("")}
`; } 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"); if (!panel || !list || !count) return; const commands = result ? parsedCanCommands(result) : []; count.textContent = String(commands.length); panel.dataset.state = commands.length ? "ready" : message ? "pending" : "empty"; if (commands.length) { list.innerHTML = `
    ${commands.map((item) => `
  1. ${escapeHtml(item.channel)} ${item.parseSource ? `${parsedCommandSourceLabel(item.parseSource)}` : ""} ${escapeHtml(item.deliveryLabel)} ${escapeHtml(item.category)}
    语义控制指令 ${escapeHtml(item.semantic)}
    实际控制指令 ${escapeHtml(item.command)}
    ${(item.frames || (item.frame ? [{ label: item.frameLabel || "控制帧", value: item.frame }] : [])).map((frame) => `${escapeHtml(frame.label)} · ${escapeHtml(frame.value)}`).join("")}
  2. `).join("")}
`; } else { list.innerHTML = `

${escapeHtml(message || "本次请求未解析出控制指令")}

`; } 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); } function renderDirectCanCommands(commands, deviceName) { const panel = $("#v2CanCommandPanel"); const list = $("#v2CanCommandList"); const count = $("#v2CanCommandCount"); if (!panel || !list || !count) return; const items = commands.map((command) => ({ channel: command.startsWith("seat passenger ") ? "CAN0" : "CAN1", command, deliveryState: "not_submitted", deliveryLabel: "未下发", })); count.textContent = String(items.length); panel.dataset.state = "ready"; list.innerHTML = `
    ${items.map((item) => `
  1. ${item.channel} 设备控件 ${item.deliveryLabel} ${escapeHtml(deviceName)} ${escapeHtml(item.command)}
  2. `).join("")}
`; validatedCommandsVisible = true; validatedCommandContext = { requestId: "", utterance: items.map((item) => item.command).join(";"), }; requestAnimationFrame(syncSidebarLogBaseline); } function closeReasoningPicker() { reasoningPickerControl?.close(); } function installReasoningPicker(reasoningField, originalForm) { const composer = $(".command-composer", originalForm); if (!composer || !reasoningField) return; reasoningField.classList.add("v2-reasoning-field"); reasoningField.hidden = true; const trigger = make("button", "v2-reasoning-trigger", ` 模型智能 均衡 `); trigger.id = "v2ReasoningTrigger"; trigger.type = "button"; trigger.setAttribute("aria-haspopup", "listbox"); trigger.setAttribute("aria-expanded", "false"); trigger.setAttribute("aria-controls", "reasoningField"); trigger.setAttribute("aria-label", "选择模型智能程度,当前均衡"); $(".reasoning-segmented", reasoningField)?.setAttribute("role", "listbox"); const options = () => $$('[data-reasoning-effort]', reasoningField); const place = () => { if (reasoningField.hidden) return; const triggerBox = trigger.getBoundingClientRect(); const width = Math.min(320, Math.max(250, window.innerWidth - 24)); const left = Math.min( Math.max(12, triggerBox.left), Math.max(12, window.innerWidth - width - 12), ); reasoningField.style.width = `${width}px`; reasoningField.style.left = `${left}px`; const height = reasoningField.offsetHeight; const above = triggerBox.top - height - 8; const top = above >= 12 ? above : Math.min(triggerBox.bottom + 8, window.innerHeight - height - 12); reasoningField.style.top = `${Math.max(12, top)}px`; }; const sync = () => { const selected = options().find((button) => button.classList.contains("active")); const label = selected?.querySelector("strong")?.textContent?.trim() || "均衡"; const unavailable = options().length > 0 && options().every((button) => button.disabled); $("#v2ReasoningValue", trigger).textContent = label; trigger.classList.toggle("unavailable", unavailable); trigger.setAttribute("aria-disabled", String(unavailable)); trigger.setAttribute("aria-label", `选择模型智能程度,当前${label}`); options().forEach((button) => { button.setAttribute("role", "option"); button.setAttribute("aria-selected", String(button === selected)); }); }; const close = () => { reasoningField.hidden = true; trigger.classList.remove("open"); trigger.setAttribute("aria-expanded", "false"); }; const open = () => { reasoningField.hidden = false; trigger.classList.add("open"); trigger.setAttribute("aria-expanded", "true"); place(); }; reasoningPickerControl = { close, open, place, sync, trigger }; const resetButton = $("#newConversation", originalForm); const runButton = $("#runButton", originalForm); if (resetButton) composer.append(resetButton); composer.append(trigger); if (runButton) composer.append(runButton); document.body.append(reasoningField); trigger.addEventListener("click", (event) => { event.stopPropagation(); if (reasoningField.hidden) open(); else close(); }); reasoningField.addEventListener("click", (event) => { event.stopPropagation(); if (!event.target.closest("[data-reasoning-effort]")) return; requestAnimationFrame(() => { sync(); close(); trigger.focus({ preventScroll: true }); }); }); new MutationObserver(sync).observe(reasoningField, { attributes: true, attributeFilter: ["class", "disabled"], subtree: true, }); window.addEventListener("resize", place); sync(); } function buildSidebar(commandPanel) { commandPanel.classList.add("v2-sidebar"); commandPanel.id = "v2SidebarPanel"; commandPanel.innerHTML = ""; const sidebarToggle = make("button", "v2-sidebar-toggle v2-panel-control-button", '左侧控制台'); sidebarToggle.id = "v2SidebarToggle"; sidebarToggle.type = "button"; sidebarToggle.setAttribute("aria-controls", commandPanel.id); sidebarToggle.setAttribute("aria-label", "展开左侧控制台"); sidebarToggle.setAttribute("aria-pressed", "false"); sidebarToggle.setAttribute("aria-expanded", "false"); sidebarToggle.title = "展开左侧控制台"; const footer = make("footer", "v2-sidebar-footer", `
智舱控制链路正在连接服务
`); commandPanel.append(sidebarToggle, footer); } function prepareRuntimeControls(originalForm) { const vehicleField = $(".vehicle-field", originalForm); const transportField = $("#vehicleTransportField", originalForm); const providerField = $(".field-group:has(.segmented [data-provider])", originalForm); const reasoningField = $("#reasoningField", originalForm); const executionField = $(".field-group:has(.execution-mode-options)", originalForm); const simulationFaultField = $("#simulationFaultField", originalForm); transportField?.classList.add("v2-transport-field"); executionField?.classList.add("v2-execution-field"); simulationFaultField?.classList.add("v2-simulation-fault-field"); installReasoningPicker(reasoningField, originalForm); [vehicleField, providerField].forEach((field) => { if (!field) return; field.classList.add("v2-runtime-data-field"); field.hidden = true; }); } function buildOperation(mainPanel, originalForm, panelHeading, requestHeader, gestureDrawer) { const operation = make("section", "v2-operation-shell", `
`); mainPanel.prepend(operation); const naturalPanel = $("#v2NaturalPanel"); if (panelHeading) { panelHeading.classList.add("v2-operation-heading", "v2-module-heading"); const title = $("h2", panelHeading); if (title) title.textContent = "AI助手"; naturalPanel.append(panelHeading); } const naturalGrid = make("div", "v2-natural-grid", ""); naturalGrid.id = "v2NaturalGrid"; originalForm.classList.add("v2-request-form"); naturalGrid.append(originalForm); if (requestHeader) { requestHeader.classList.add("v2-request-status"); naturalGrid.append(requestHeader); } naturalPanel.append(naturalGrid); const gesturePanel = $("#v2GesturePanel"); const gestureHeading = make("header", "v2-operation-heading v2-module-heading v2-gesture-operation-heading", `

抬抬手,控制整车

`); gesturePanel.append(gestureHeading); if (gestureDrawer) { gestureDrawer.classList.add("v2-inline-gesture"); const cameraStage = $(".gesture-camera-stage", gestureDrawer); const cameraToggle = $("#gestureStartButton", gestureDrawer); const actionRail = $(".gesture-actions", gestureDrawer); const statusStrip = $(".gesture-status-strip", gestureDrawer); const gestureNow = $(".gesture-now", gestureDrawer); const guideCard = $(".gesture-guide-card", gestureDrawer); if (cameraToggle) { cameraToggle.classList.add("v2-camera-toggle"); cameraToggle.setAttribute("aria-label", "开启摄像头"); cameraToggle.setAttribute("title", "开启摄像头"); } if (actionRail && cameraToggle) { actionRail.classList.add("v2-gesture-action-rail"); actionRail.append(cameraToggle); } const resultStack = make("div", "v2-gesture-result-stack", ""); resultStack.id = "v2GestureResultStack"; const recognitionCard = make("section", "v2-gesture-recognition-card", ` 手势识别结果`); const executionSlot = make("section", "v2-gesture-execution-slot", ` 执行结果`); executionSlot.id = "v2GestureExecutionSlot"; if (statusStrip) recognitionCard.append(statusStrip); if (gestureNow) recognitionCard.append(gestureNow); resultStack.append(recognitionCard, executionSlot); if (cameraStage) gestureDrawer.append(cameraStage); if (actionRail) gestureDrawer.append(actionRail); gestureDrawer.append(resultStack); if (guideCard) gestureDrawer.append(guideCard); gesturePanel.append(gestureDrawer); } return operation; } function buildModuleSwitcher(mainPanel, modeRail) { const switcher = make("nav", "v2-module-switcher", ""); switcher.id = "v2ModuleSwitcher"; switcher.dataset.unifiedSync = "true"; switcher.setAttribute("aria-label", "页面板块展开与收起"); switcher.innerHTML = ` `; const modeRailBar = $(".mode-rail-bar", modeRail); if (modeRailBar) modeRailBar.prepend(switcher); else mainPanel.prepend(switcher); const panelDock = $("#v2PanelControlDock", switcher); const sidebarToggle = $("#v2SidebarToggle"); if (panelDock && sidebarToggle) panelDock.append(sidebarToggle); } function buildOutput(mainPanel, modeRail) { const output = make("section", "v2-output-shell", `
00

整车联合数字孪生

LIVE STATE
`); mainPanel.append(output); if (modeRail) { modeRail.classList.add("v2-mode-rail"); mainPanel.insertBefore(modeRail, output); } const content = $("#v2OutputContent"); const twin = $(".joint-twin-shell", mainPanel); const resultWorkspace = $(".result-workspace", mainPanel); if (twin) content.append(twin); if (resultWorkspace) content.append(resultWorkspace); pageDefinitions.forEach((page) => { const target = $(page.target, content); if (!target) return; target.dataset.v2Page = String(page.index); target.classList.add("v2-output-page"); }); pageDefinitions.forEach((page) => { const button = make("button", "v2-page-button", ` ${String(page.index).padStart(2, "0")} ${page.shortLabel}`); button.type = "button"; button.dataset.v2PageTarget = String(page.index); button.setAttribute("aria-label", `${String(page.index).padStart(2, "0")} ${page.label}`); $("#v2PageRail").append(button); }); } const displaySync = { revision: -1, requestId: null, pollTimer: null, deviceFrame: null, deviceFingerprint: "", activeDevice: null, pinnedDevice: null, previewClearTimer: null, hoverSuppressedUntil: 0, controlDevice: null, deviceDrafts: {}, pendingControls: {}, logs: [], 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; const clampControlNumber = (value, min, max, fallback = min) => { const numeric = Number.parseFloat(String(value ?? "").replace(/[^\d.-]/g, "")); return Number.isFinite(numeric) ? Math.max(min, Math.min(max, numeric)) : fallback; }; const splitReadout = (selector) => { const [front = "-", rear = "-"] = readText(selector, "- / -").split("/"); return [front.trim() || "-", rear.trim() || "-"]; }; function seatFunctionValue(seat, feature) { return $(`.seat-card[data-seat="${seat}"] [data-function="${feature}"] b`)?.textContent?.trim() || "--"; } function seatAxisValue(seat, axis) { return $(`.seat-card[data-seat="${seat}"] [data-axis="${axis}"] b`)?.textContent?.trim() || "--"; } function seatLevelControlValue(seat, feature) { const text = seatFunctionValue(seat, feature); const level = text.match(/L(\d+)/i)?.[1]; return /OFF|关闭|--/.test(text) ? "off" : level === "2" ? "low" : "high"; } function massageReadout() { const text = seatFunctionValue("driver", "massage"); if (text === "--") return ["--", "--"]; if (/OFF|关闭/.test(text)) return ["关闭", "关闭"]; const [mode = "开启", strength = "默认"] = text.split("·").map((item) => item.trim()); return [mode, strength]; } const levelOptions = [ ["off", "关闭"], ["high", "1 档(高)"], ["low", "2 档(低)"], ]; const toggleOptions = [["off", "关闭"], ["on", "开启"]]; const massageModeOptions = [ ["off", "关闭"], ["relax", "放松"], ["stretch", "伸展"], ["slack", "松弛"], ["wave", "波浪"], ["pulse", "脉冲"], ]; const massageStrengthOptions = [ ["off", "关闭"], ["weak", "低"], ["medium", "中"], ["strong", "高"], ]; const colorIndexNames = ["皓月白", "冰川蓝", "松石绿", "星云紫", "琥珀橙", "玫瑰红"]; const colorIndexOptions = Array.from({ length: 127 }, (_item, index) => [ String(index + 1), colorIndexNames[index] ? `${index + 1} · ${colorIndexNames[index]}` : `色号 ${index + 1}`, ]); const rangeControl = (key, label, value, min = 0, max = 100, step = 1, unit = "%", hint = "") => ({ key, label, type: "range", value: clampControlNumber(value, min, max), min, max, step, unit, hint, }); const selectControl = (key, label, value, options) => ({ key, label, type: "select", value, options }); const hasControlValue = (values, key) => Object.hasOwn(values, key); function normalizeDeviceReadout(value) { const text = String(value ?? "").trim(); if (!text || /^-+$/.test(text) || /^--\s*/.test(text)) return "-"; if (/未知|等待反馈|同步中|状态未知/.test(text)) return "-"; return text; } function ambientPowerState() { const value = $('[data-domain-panel="ambient_light"]')?.dataset.ambientPower; return value === "on" || value === "off" ? value : "unknown"; } function selectedTransportHasConnection() { const selected = $('.vehicle-transport-options [data-control-transport][aria-pressed="true"]') || $('.vehicle-transport-options [data-control-transport].active'); return selected?.dataset.connectionState === "online"; } function deviceFeedbackAvailable(deviceId) { if (!selectedTransportHasConnection()) return false; return deviceId === "passenger" ? document.body.dataset.passengerFeedbackState === "online" : document.body.dataset.vehicleFeedbackState === "online"; } function deviceReadoutRows(device) { const rows = device.rows(); if (!deviceFeedbackAvailable(device.id)) { return rows.map(([label]) => [label, "-"]); } return rows.map(([label, value]) => [label, normalizeDeviceReadout(value)]); } function deviceControlValue(deviceId, control) { const draft = displaySync.deviceDrafts[deviceId]; return draft && Object.hasOwn(draft, control.key) ? draft[control.key] : control.value; } const syncedDevices = [ { id: "passenger", name: "副驾座椅", icon: "armchair", rows: () => [["前后", seatAxisValue("passenger", "longitudinal")], ["高度", seatAxisValue("passenger", "cushion_tilt")], ["靠背", seatAxisValue("passenger", "backrest_recline")], ["腿托", seatAxisValue("passenger", "legrest_height")]], controls: () => [ rangeControl("longitudinal", "前后", seatAxisValue("passenger", "longitudinal")), rangeControl("cushion_tilt", "高度", seatAxisValue("passenger", "cushion_tilt")), rangeControl("backrest_recline", "靠背", seatAxisValue("passenger", "backrest_recline")), rangeControl("legrest_height", "腿托", seatAxisValue("passenger", "legrest_height")), ], commands: (values) => [ ["longitudinal", "horizontal"], ["cushion_tilt", "basin"], ["backrest_recline", "backrest"], ["legrest_height", "legrest"], ].filter(([key]) => hasControlValue(values, key)) .map(([key, axis]) => `seat passenger ${axis} position ${Math.round(Number(values[key]))}`), }, { id: "driver", name: "主驾座椅", icon: "armchair", rows: () => { const [massageMode, massageStrength] = massageReadout(); return [["通风", seatFunctionValue("driver", "ventilation")], ["加热", seatFunctionValue("driver", "heating")], ["按摩模式", massageMode], ["按摩强度", massageStrength]]; }, controls: () => { const [massageMode, massageStrength] = massageReadout(); return [ selectControl("ventilation", "通风", seatLevelControlValue("driver", "ventilation"), levelOptions), selectControl("heating", "加热", seatLevelControlValue("driver", "heating"), levelOptions), selectControl("massage_mode", "按摩模式", /关闭|--/.test(massageMode) ? "off" : massageMode.toLowerCase(), massageModeOptions), selectControl("massage_strength", "按摩强度", /关闭|--/.test(massageStrength) ? "off" : massageStrength.toLowerCase(), massageStrengthOptions), ]; }, commands: (values) => { const commands = []; if (hasControlValue(values, "ventilation")) commands.push(`seat driver vent ${values.ventilation}`); if (hasControlValue(values, "heating")) commands.push(`seat driver heat ${values.heating}`); if (hasControlValue(values, "massage_mode")) { commands.push(values.massage_mode === "off" ? "seat driver massage off" : `seat driver massage mode ${values.massage_mode}`); } if (hasControlValue(values, "massage_strength")) { commands.push(values.massage_strength === "off" ? "seat driver massage off" : `seat driver massage strength ${values.massage_strength}`); } return [...new Set(commands)]; }, }, { id: "windows", name: "车窗", icon: "gallery-vertical-end", rows: () => [["左前窗", readText("#frontLeftWindow")], ["右前窗", readText("#frontRightWindow")], ["左后窗", readText("#rearLeftWindow")], ["右后窗", readText("#rearRightWindow")]], controls: () => [ rangeControl("front_left", "左前窗", readText("#frontLeftWindow"), 0, 100, 1, "%", "0% 关闭 · 100% 打开"), rangeControl("front_right", "右前窗", readText("#frontRightWindow")), rangeControl("rear_left", "左后窗", readText("#rearLeftWindow")), rangeControl("rear_right", "右后窗", readText("#rearRightWindow")), ], commands: (values) => Object.entries({ front_left: "front-left", front_right: "front-right", rear_left: "rear-left", rear_right: "rear-right", }).filter(([key]) => hasControlValue(values, key)) .map(([key, target]) => `window ${target} position ${Math.round(Number(values[key]))}`), }, { id: "shade", name: "遮阳帘", icon: "panels-top-left", rows: () => [["当前位置", readText("#shadePositionValue")]], controls: () => [rangeControl("position", "当前位置", readText("#shadePositionValue"), 0, 100, 1, "%", "0% 关闭 · 100% 打开")], commands: (values) => hasControlValue(values, "position") ? [`shade position ${Math.round(Number(values.position))}`] : [], }, { id: "reading", name: "阅读灯", icon: "book-open", rows: () => [["开关状态", readText("#interiorLightPower", "等待反馈")]], controls: () => [selectControl("power", "开关状态", /开启|ON/.test(readText("#interiorLightPower")) ? "on" : "off", toggleOptions)], commands: (values) => hasControlValue(values, "power") ? [`light reading all ${values.power}`] : [], }, { id: "ambient", name: "氛围灯", icon: "sparkles", rows: () => { const [frontBrightness, rearBrightness] = splitReadout("#ambientBrightness"); const [frontColor, rearColor] = splitReadout("#ambientColorIndex"); const powerState = ambientPowerState(); const power = powerState === "on" ? "开启" : powerState === "off" ? "关闭" : "-"; return [["开关状态", power], ["前排亮度", frontBrightness], ["前排颜色", frontColor], ["后排亮度", rearBrightness], ["后排颜色", rearColor]]; }, controls: () => { const [frontBrightness, rearBrightness] = splitReadout("#ambientBrightness"); const [frontColor, rearColor] = splitReadout("#ambientColorIndex"); return [ selectControl("power", "开关状态", ambientPowerState() === "off" ? "off" : "on", toggleOptions), rangeControl("brightness", "全车亮度", frontBrightness === "-" ? rearBrightness : frontBrightness, 1, 5, 1, " 级"), selectControl("color", "全车颜色", String(clampControlNumber(frontColor === "-" ? rearColor : frontColor, 1, 127, 1)), colorIndexOptions), ]; }, commands: (values) => [ ...(hasControlValue(values, "power") ? [`light ambient ${values.power}`] : []), ...(hasControlValue(values, "brightness") ? [`light ambient brightness ${Math.round(Number(values.brightness))}`] : []), ...(hasControlValue(values, "color") ? [`light ambient color ${Math.round(Number(values.color))}`] : []), ], }, { id: "headlight", name: "前大灯", icon: "car-front", controllable: false, rows: () => [["近光灯", readText("#lowBeamState")], ["远光灯", readText("#highBeamState")], ["雾灯", readText("#fogLightState")], ["转向灯", readText("#turnSignalState")]], controls: () => [["low_beam", "近光灯", "#lowBeamState"], ["high_beam", "远光灯", "#highBeamState"], ["fog_light", "雾灯", "#fogLightState"], ["turn_signal", "转向灯", "#turnSignalState"]].map(([key, label, selector]) => selectControl(key, label, /开启|ON/.test(readText(selector)) ? "on" : "off", toggleOptions)), }, { id: "climate", name: "空调", icon: "fan", rows: () => [["开关状态", readText("#climatePower", "等待反馈")], ["主驾温度", readText("#driverTemperature")], ["副驾温度", readText("#passengerTemperature")], ["风量", readText("#fanLevel")], ["循环模式", readText("#cycleMode")]], controls: () => [ selectControl("power", "开关状态", /ON|ACTIVE|开启/.test(readText("#climatePower")) ? "on" : "off", toggleOptions), rangeControl("driver_temperature", "主驾温度", Math.round(clampControlNumber(readText("#driverTemperature"), 18, 32, 24)), 18, 32, 1, "℃"), rangeControl("passenger_temperature", "副驾温度", Math.round(clampControlNumber(readText("#passengerTemperature"), 18, 32, 24)), 18, 32, 1, "℃"), rangeControl("fan_level", "风量", readText("#fanLevel"), 1, 7, 1, " 档"), selectControl("cycle_mode", "循环模式", readText("#cycleMode").includes("内") ? "internal" : "external", [["internal", "内循环"], ["external", "外循环"]]), ], commands: (values) => [ ...(hasControlValue(values, "power") ? [`ac ${values.power}`] : []), ...(hasControlValue(values, "driver_temperature") ? [`ac temp driver ${Math.round(Number(values.driver_temperature))}`] : []), ...(hasControlValue(values, "passenger_temperature") ? [`ac temp passenger ${Math.round(Number(values.passenger_temperature))}`] : []), ...(hasControlValue(values, "fan_level") ? [`ac fan ${Math.round(Number(values.fan_level))}`] : []), ...(hasControlValue(values, "cycle_mode") ? [`ac cycle ${values.cycle_mode}`] : []), ], }, ]; function renderDeviceControl(device, control) { const value = deviceControlValue(device.id, control); if (control.type === "select") { const options = control.options.map(([optionValue, label]) => ``).join(""); return ``; } return ``; } function renderSyncedDevices() { const grid = $("#v2DeviceSyncGrid"); if (!grid) return; grid.innerHTML = syncedDevices.map((device) => { const rows = deviceReadoutRows(device); const waiting = rows.some(([, value]) => value === "-"); const controllable = device.controllable !== false; const controlOpen = controllable && displaySync.controlDevice === device.id; const hasDraft = Boolean(displaySync.deviceDrafts[device.id]); const pending = Boolean(displaySync.pendingControls[device.id]); const stateLabel = pending ? "已发送 · 等待同步" : hasDraft ? "参数待发送" : `${rows.length} 项关键状态`; return `
${rows.map(([label, value]) => `
${escapeHtml(label)}
${escapeHtml(value)}
`).join("")}
手动调节参数确认后通过编排链路发送至车机
${device.controls().map((control) => renderDeviceControl(device, control)).join("")}
`; }).join(""); if (displaySync.activeDevice) { $(`.v2-synced-device[data-device="${displaySync.activeDevice}"]`)?.classList.add("active"); } displaySync.deviceFingerprint = JSON.stringify(syncedDevices.map((device) => [device.id, deviceReadoutRows(device)])); window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } }); } function storeDeviceControlDraft(input) { const deviceId = input.dataset.deviceControl; const key = input.dataset.controlKey; if (!deviceId || !key) return; displaySync.deviceDrafts[deviceId] ||= {}; displaySync.deviceDrafts[deviceId][key] = input.value; const controls = input.closest("[data-device-controls]"); const output = controls?.querySelector(`[data-control-output="${key}"]`); if (output) output.textContent = `${input.value}${input.dataset.controlUnit || ""}`; const card = input.closest("[data-device]"); if (card) { card.dataset.hasDraft = "true"; const state = $("[data-device-card-state]", card); if (state) state.textContent = "参数待发送"; } window.dispatchEvent(new CustomEvent("cognitive-agent:vehicle-control-draft", { detail: { device_id: deviceId, control: key, value: input.value, source: "web_console" }, })); } async function sendDeviceControl(deviceId) { const device = syncedDevices.find((item) => item.id === deviceId); if (!device) return; if (device.controllable === false || typeof device.commands !== "function") { window.CognitiveAgentConsole?.notify?.(`${device.name}当前只有状态反馈,尚无已验证的 CAN 控制白名单`); return; } const draft = displaySync.deviceDrafts[device.id]; if (!draft || !Object.keys(draft).length) { window.CognitiveAgentConsole?.notify?.("请先修改需要发送的参数"); return; } if (displaySync.pendingControls[device.id]) { window.CognitiveAgentConsole?.notify?.("该设备的上一组 CAN 指令仍在执行"); return; } const values = Object.fromEntries(Object.entries(draft).map(([key, value]) => [key, String(value)])); const commands = device.commands(values).filter(Boolean); if (!commands.length) { window.CognitiveAgentConsole?.notify?.("修改项没有对应的 CAN 白名单控制指令"); return; } const commandSummary = commands.join(";"); const detail = { device_id: device.id, device_name: device.name, controls: values, can_commands: commands, source: "web_console", requested_at: new Date().toISOString(), }; const execute = window.CognitiveAgentConsole?.executeCanCommands; if (typeof execute !== "function") { window.CognitiveAgentConsole?.notify?.("当前页面尚未加载 CAN 直控接口"); return; } window.dispatchEvent(new CustomEvent("cognitive-agent:vehicle-can-control-request", { detail })); displaySync.pendingControls[device.id] = commandSummary; appendSyncLog({ category: "device", title: `网页端直控${device.name}`, detail: commandSummary, status: "info" }); renderDirectCanCommands(commands, device.name); renderSyncedDevices(); try { await execute(commands, { deviceName: device.name }); delete displaySync.deviceDrafts[device.id]; appendSyncLog({ category: "device", title: `${device.name} CAN 控制已执行`, detail: commandSummary, status: "success", }); } catch (error) { appendSyncLog({ category: "device", title: `${device.name} CAN 控制发送失败`, detail: error?.message || "CAN 控制请求失败", status: "error", }); window.CognitiveAgentConsole?.notify?.(error?.message || "CAN 控制请求失败"); } finally { delete displaySync.pendingControls[device.id]; renderSyncedDevices(); } } function scheduleSyncedDeviceRender() { if (displaySync.deviceFrame) return; displaySync.deviceFrame = requestAnimationFrame(() => { displaySync.deviceFrame = null; const fingerprint = JSON.stringify(syncedDevices.map((device) => [device.id, deviceReadoutRows(device)])); if (fingerprint === displaySync.deviceFingerprint && $("#v2DeviceSyncGrid")?.childElementCount) return; renderSyncedDevices(); if (displaySync.activeDevice) presentTwinDevice(displaySync.activeDevice, { pinned: Boolean(displaySync.pinnedDevice), selectSeat: false, }); }); } function appendSyncLog({ key = "", category = "sync", title, detail = "", status = "info" }) { if (!title || (key && displaySync.logKeys.has(key))) return; const item = { key, category, title, detail, status, time: new Intl.DateTimeFormat("zh-CN", { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false, }).format(new Date()), }; if (key) displaySync.logKeys.add(key); displaySync.logs.unshift(item); if (displaySync.logs.length > 40) { const removed = displaySync.logs.pop(); if (removed?.key) displaySync.logKeys.delete(removed.key); } renderSyncLogs(); } function renderSyncLogs() { const list = $("#v2SyncLogList"); if (!list) return; const filter = $("#v2LogFilter")?.value || "all"; const items = displaySync.logs.filter((item) => filter === "all" || item.category === filter); const icons = { task: "route", sync: "refresh-cw", extension: "usb", device: "activity" }; list.innerHTML = items.length ? items.map((item) => `
  • ${escapeHtml(item.title)}${escapeHtml(item.detail)}
  • `).join("") : '
  • 暂无记录车机任务、设备反馈与外设事件会显示在这里
  • '; window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } }); } function panelVisible(key) { const panel = key === "devices" ? $("#v2DevicePanel") : $("#v2ExtensionPanel"); if (panel) return !panel.classList.contains("is-collapsed"); return sessionStorage.getItem(`cognitive-agent.sync-panel.${key}`) !== "hidden"; } function setSidePanelVisibility(key, visible) { const panel = key === "devices" ? $("#v2DevicePanel") : $("#v2ExtensionPanel"); if (!panel) return; const panelName = key === "devices" ? "车端设备状态" : "外设与扩展能力"; panel.hidden = !visible; panel.classList.toggle("is-collapsed", !visible); panel.dataset.collapsed = String(!visible); sessionStorage.setItem(`cognitive-agent.sync-panel.${key}`, visible ? "visible" : "hidden"); $$(`[data-panel-toggle="${key}"], [data-panel-close="${key}"]`).forEach((button) => { button.classList.toggle("active", visible); button.setAttribute("aria-pressed", String(visible)); button.setAttribute("aria-expanded", String(visible)); button.setAttribute("aria-label", `${visible ? "收起" : "展开"}${panelName}`); button.title = `${visible ? "收起" : "展开"}${panelName}`; const actionLabel = $("[data-panel-action-label]", button); if (actionLabel) actionLabel.textContent = visible ? "收起" : "展开"; const stateLabel = $("[data-panel-state-label]", button); if (stateLabel) stateLabel.textContent = visible ? "已展开" : "已收起"; }); const openPanels = ["devices", "extensions"].filter((panelKey) => { const candidate = panelKey === "devices" ? $("#v2DevicePanel") : $("#v2ExtensionPanel"); return candidate && !candidate.classList.contains("is-collapsed"); }); $(".joint-twin-shell")?.setAttribute("data-open-panels", openPanels.join(" ") || "none"); const rightRail = $(".v2-unified-right-rail"); if (rightRail) { const collapsed = openPanels.length === 0; rightRail.hidden = collapsed; rightRail.classList.toggle("is-collapsed", collapsed); rightRail.toggleAttribute("inert", collapsed); rightRail.dataset.openPanels = openPanels.join(" ") || "none"; } window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } }); window.dispatchEvent(new Event("resize")); } const twinPopoverClamp = (value, minimum, maximum) => ( maximum < minimum ? minimum : Math.max(minimum, Math.min(maximum, value)) ); function twinPopoverIntersectionArea(first, second) { const width = Math.max(0, Math.min(first.right, second.right) - Math.max(first.left, second.left)); const height = Math.max(0, Math.min(first.bottom, second.bottom) - Math.max(first.top, second.top)); return width * height; } function positionTwinDevicePopover(deviceId = displaySync.activeDevice) { const stage = $("#seatTwin"); const popover = $("#v2TwinDevicePopover"); const hotspot = deviceId ? $(`[data-twin-device="${deviceId}"]`, stage) : null; const hotspotLabel = hotspot ? $(".v2-twin-hotspot-label", hotspot) : null; if (!stage || !popover || popover.hidden || !hotspot || !hotspotLabel) return null; if (hotspot.hidden) { popover.dataset.anchorVisible = "false"; if (popover.dataset.pinned === "false") popover.hidden = true; return null; } popover.dataset.anchorVisible = "true"; const stageRect = stage.getBoundingClientRect(); const labelClientRect = hotspotLabel.getBoundingClientRect(); const popoverWidth = popover.offsetWidth; const popoverHeight = popover.offsetHeight; if (!stageRect.width || !stageRect.height || !popoverWidth || !popoverHeight) return null; const margin = stageRect.width < 520 ? 8 : 12; const gap = stageRect.width < 520 ? 8 : 12; const bounds = { left: margin, top: margin, right: Math.max(margin, stageRect.width - margin), bottom: Math.max(margin, stageRect.height - margin), }; const labelRect = { left: labelClientRect.left - stageRect.left, top: labelClientRect.top - stageRect.top, right: labelClientRect.right - stageRect.left, bottom: labelClientRect.bottom - stageRect.top, }; labelRect.width = labelRect.right - labelRect.left; labelRect.height = labelRect.bottom - labelRect.top; labelRect.centerX = (labelRect.left + labelRect.right) / 2; labelRect.centerY = (labelRect.top + labelRect.bottom) / 2; const toStageRect = (element, padding = 0) => { const rect = element.getBoundingClientRect(); return { left: rect.left - stageRect.left - padding, top: rect.top - stageRect.top - padding, right: rect.right - stageRect.left + padding, bottom: rect.bottom - stageRect.top + padding, }; }; const visibleElement = (element) => { const rect = element.getBoundingClientRect(); return rect.width > 0 && rect.height > 0; }; const labelObstacles = $$(".v2-twin-hotspot-label", stage) .filter((element) => element !== hotspotLabel && visibleElement(element)) .map((element) => ({ deviceId: element.closest("[data-twin-device]")?.dataset.twinDevice || "", rect: toStageRect(element, 3), })); const chromeObstacles = $$([".twin-seat-label", ".twin-orientation", ".twin-readout:not([hidden])"].join(","), stage) .filter(visibleElement) .map((element) => toStageRect(element, 5)); const activeLabelObstacle = { left: labelRect.left - 10, top: labelRect.top - 10, right: labelRect.right + 10, bottom: labelRect.bottom + 10, }; const geometrySignature = [ deviceId, popover.dataset.pinned, Math.round(stageRect.width), Math.round(stageRect.height), Math.round(popoverWidth), Math.round(popoverHeight), Math.round(labelRect.left), Math.round(labelRect.top), ...labelObstacles.flatMap(({ deviceId: obstacleDeviceId, rect }) => [ obstacleDeviceId, Math.round(rect.left), Math.round(rect.top), ]), ...chromeObstacles.flatMap((rect) => [Math.round(rect.left), Math.round(rect.top)]), ].join(":"); if (popover.dataset.layoutSignature === geometrySignature) return null; popover.dataset.layoutSignature = geometrySignature; const availableSpace = { right: bounds.right - labelRect.right, left: labelRect.left - bounds.left, bottom: bounds.bottom - labelRect.bottom, top: labelRect.top - bounds.top, }; const sideOrder = Object.keys(availableSpace).sort((first, second) => ( availableSpace[second] - availableSpace[first] )); const rawCandidates = []; const pushSideCandidates = (side) => { const horizontal = side === "left" || side === "right"; const alignments = horizontal ? [ [labelRect.centerY - popoverHeight / 2, "center"], [labelRect.top, "start"], [labelRect.bottom - popoverHeight, "end"], ] : [ [labelRect.centerX - popoverWidth / 2, "center"], [labelRect.left, "start"], [labelRect.right - popoverWidth, "end"], ]; alignments.forEach(([crossAxis, alignment], alignmentIndex) => { let left = crossAxis; let top = crossAxis; if (side === "right") { left = labelRect.right + gap; top = crossAxis; } else if (side === "left") { left = labelRect.left - gap - popoverWidth; top = crossAxis; } else if (side === "bottom") { left = crossAxis; top = labelRect.bottom + gap; } else { left = crossAxis; top = labelRect.top - gap - popoverHeight; } rawCandidates.push({ left, top, side, alignment, alignmentIndex, anchored: true }); }); }; sideOrder.forEach(pushSideCandidates); const maximumLeft = bounds.right - popoverWidth; const maximumTop = bounds.bottom - popoverHeight; [bounds.left, labelRect.left - gap - popoverWidth, labelRect.centerX - popoverWidth / 2, labelRect.right + gap, maximumLeft].forEach((left) => { [bounds.top, labelRect.top - gap - popoverHeight, labelRect.centerY - popoverHeight / 2, labelRect.bottom + gap, maximumTop].forEach((top) => { rawCandidates.push({ left, top, side: "adaptive", alignment: "free", alignmentIndex: 0, anchored: false }); }); }); const seen = new Set(); const previousPlacement = popover.dataset.anchorDevice === deviceId ? `${popover.dataset.placement || ""}:${popover.dataset.alignment || ""}` : ""; const candidates = rawCandidates.map((candidate) => { const left = twinPopoverClamp(candidate.left, bounds.left, maximumLeft); const top = twinPopoverClamp(candidate.top, bounds.top, maximumTop); const key = `${Math.round(left)}:${Math.round(top)}`; if (seen.has(key)) return null; seen.add(key); const rect = { left, top, right: left + popoverWidth, bottom: top + popoverHeight, }; const clampDistance = Math.abs(left - candidate.left) + Math.abs(top - candidate.top); const activeOverlap = twinPopoverIntersectionArea(rect, activeLabelObstacle); const shadeOverlap = labelObstacles.reduce((total, obstacle) => ( total + (obstacle.deviceId === "shade" ? twinPopoverIntersectionArea(rect, obstacle.rect) : 0) ), 0); const labelOverlap = labelObstacles.reduce((total, obstacle) => ( total + (obstacle.deviceId === "shade" ? 0 : twinPopoverIntersectionArea(rect, obstacle.rect)) ), 0); const chromeOverlap = chromeObstacles.reduce((total, obstacle) => ( total + twinPopoverIntersectionArea(rect, obstacle) ), 0); const distance = Math.hypot( left + popoverWidth / 2 - labelRect.centerX, top + popoverHeight / 2 - labelRect.centerY, ); const placement = candidate.side === "adaptive" ? (Math.abs(left + popoverWidth / 2 - labelRect.centerX) > Math.abs(top + popoverHeight / 2 - labelRect.centerY) ? (left + popoverWidth / 2 >= labelRect.centerX ? "right" : "left") : (top + popoverHeight / 2 >= labelRect.centerY ? "bottom" : "top")) : candidate.side; const placementKey = `${placement}:${candidate.alignment}`; const sideRank = sideOrder.indexOf(placement); const score = activeOverlap * 1000 + shadeOverlap * 640 + labelOverlap * 36 + chromeOverlap * 12 + clampDistance * 90 + distance * 0.35 + Math.max(0, sideRank) * 70 + candidate.alignmentIndex * 8 + (candidate.anchored ? 0 : 90) - (placementKey === previousPlacement ? 28 : 0); return { ...candidate, left, top, placement, score, activeOverlap, shadeOverlap, labelOverlap }; }).filter(Boolean); const best = candidates.sort((first, second) => first.score - second.score)[0]; if (!best) return null; popover.style.left = `${Math.round(best.left)}px`; popover.style.top = `${Math.round(best.top)}px`; popover.style.removeProperty("right"); popover.style.removeProperty("bottom"); popover.dataset.anchorDevice = deviceId; popover.dataset.placement = best.placement; popover.dataset.alignment = best.alignment; popover.dataset.shadeOverlap = String(Math.round(best.shadeOverlap)); popover.dataset.labelOverlap = String(Math.round(best.labelOverlap)); return best; } function presentTwinDevice(deviceId, { pinned = false, selectSeat = pinned } = {}) { const device = syncedDevices.find((item) => item.id === deviceId); const popover = $("#v2TwinDevicePopover"); if (!device || !popover) return; if (displaySync.previewClearTimer) { window.clearTimeout(displaySync.previewClearTimer); displaySync.previewClearTimer = null; } displaySync.activeDevice = deviceId; if (pinned) displaySync.pinnedDevice = deviceId; const rows = deviceReadoutRows(device); popover.hidden = false; popover.dataset.device = deviceId; popover.dataset.pinned = String(Boolean(displaySync.pinnedDevice)); popover.innerHTML = `
    ${escapeHtml(device.name)}
    ${pinned ? "已固定" : "靠近查看"}
    ${rows.map(([label, value]) => `
    ${escapeHtml(label)}
    ${escapeHtml(value)}
    `).join("")}
    `; $$("[data-twin-device]").forEach((button) => button.classList.toggle("active", button.dataset.twinDevice === deviceId)); $$(".v2-synced-device").forEach((button) => button.classList.toggle("active", button.dataset.device === deviceId)); if (selectSeat && ["driver", "passenger"].includes(deviceId)) { $(`[data-twin-seat="${deviceId}"]`)?.click(); } window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } }); positionTwinDevicePopover(deviceId); } function clearTwinDevicePreview({ force = false } = {}) { if (displaySync.previewClearTimer) { window.clearTimeout(displaySync.previewClearTimer); displaySync.previewClearTimer = null; } if (displaySync.pinnedDevice && !force) return; displaySync.activeDevice = null; if (force) displaySync.pinnedDevice = null; const popover = $("#v2TwinDevicePopover"); if (popover) popover.hidden = true; $$("[data-twin-device], .v2-synced-device").forEach((button) => button.classList.remove("active")); } function scheduleTwinDevicePreviewClear() { if (displaySync.pinnedDevice) return; if (displaySync.previewClearTimer) window.clearTimeout(displaySync.previewClearTimer); displaySync.previewClearTimer = window.setTimeout(() => { displaySync.previewClearTimer = null; clearTwinDevicePreview(); }, 45); } function buildTwinDeviceInteractions() { const stage = $("#seatTwin"); if (!stage || $("#v2TwinHotspots")) return; const hotspots = make("div", "v2-twin-hotspots", syncedDevices.map((device) => ` `).join("")); hotspots.id = "v2TwinHotspots"; const popover = make("aside", "v2-twin-device-popover", ""); popover.id = "v2TwinDevicePopover"; popover.hidden = true; popover.setAttribute("role", "dialog"); popover.setAttribute("aria-label", "车端设备状态详情"); popover.setAttribute("aria-live", "polite"); stage.append(hotspots, popover); hotspots.addEventListener("pointerdown", (event) => event.stopPropagation()); $$("[data-twin-device]", hotspots).forEach((button) => { button.addEventListener("pointerenter", () => { if (!displaySync.pinnedDevice && performance.now() >= displaySync.hoverSuppressedUntil) { presentTwinDevice(button.dataset.twinDevice); } }); button.addEventListener("pointerleave", (event) => { if (event.relatedTarget?.closest?.("#v2TwinDevicePopover")) return; scheduleTwinDevicePreviewClear(); }); button.addEventListener("focus", () => presentTwinDevice(button.dataset.twinDevice)); button.addEventListener("blur", scheduleTwinDevicePreviewClear); }); hotspots.addEventListener("click", (event) => { const button = event.target.closest("[data-twin-device]"); if (!button) return; event.stopPropagation(); const samePinned = displaySync.pinnedDevice === button.dataset.twinDevice; if (samePinned) clearTwinDevicePreview({ force: true }); else presentTwinDevice(button.dataset.twinDevice, { pinned: true }); }); popover.addEventListener("pointerdown", (event) => event.stopPropagation()); popover.addEventListener("pointerenter", () => { if (displaySync.previewClearTimer) { window.clearTimeout(displaySync.previewClearTimer); displaySync.previewClearTimer = null; } }); popover.addEventListener("pointerleave", scheduleTwinDevicePreviewClear); popover.addEventListener("click", (event) => { if (!event.target.closest("[data-twin-popover-close]")) return; event.stopPropagation(); displaySync.hoverSuppressedUntil = performance.now() + 160; clearTwinDevicePreview({ force: true }); }); stage.addEventListener("click", (event) => { if (event.target.closest("[data-twin-device], #v2TwinDevicePopover")) return; clearTwinDevicePreview({ force: true }); }); window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } }); } function taskKind(utterance, result, source = "") { if (source === "web_console") return "网页远程编排"; if (result?.mode_profile) return "自定义功能模式"; const preset = $$(".mode-chips [data-mode-command]").find((button) => ( String(utterance).includes(button.textContent.trim()) || button.dataset.modeCommand === utterance )); return preset ? "预设功能模式" : utterance ? "一句话自定义编排" : "等待编排任务"; } function renderTaskTimeline(task) { const timeline = $("#v2TaskTimeline"); if (!timeline) return; const result = task?.result; const trace = Array.isArray(result?.trace_events) ? result.trace_events : []; const domains = Object.keys(result?.function_configs || {}); const stages = task?.status === "running" ? ["接收编排指令", "解析意图", "构建功能模式", "等待设备反馈"] : trace.length ? trace.slice(-4).map((item) => item.label || item.stage || item.event || "执行步骤") : domains.length ? ["接收编排指令", `编排 ${domains.length} 个设备域`, "生成执行计划", "同步执行结果"] : ["等待车机或网页端下发编排任务"]; timeline.innerHTML = stages.map((stage, index) => ` ${String(index + 1).padStart(2, "0")}${escapeHtml(stage)} `).join(""); } function setTaskPresentation(task = {}) { const utterance = String(task.utterance || ""); const result = task.result && typeof task.result === "object" ? task.result : null; const input = $("#utterance"); if (input && utterance && document.activeElement !== input && !document.body.classList.contains("assistant-chat-enabled")) input.value = utterance; const kind = taskKind(utterance, result, String(task.source || "")); if ($("#v2TaskKind")) $("#v2TaskKind").textContent = kind; if ($("#v2TaskCommand")) $("#v2TaskCommand").textContent = utterance || "等待车机端或网页端选择预设模式、输入一句话指令"; const normalizedStatus = String(task.status || "idle").toLowerCase(); const verificationStatus = String( result?.execution_outcome || result?.execution_report?.spec?.verification_status || "", ); const submittedUnverified = verificationStatus === "submitted_unverified"; const statusMap = { idle: ["等待任务", "idle"], running: ["构建 / 执行中", "running"], succeeded: ["执行完成", "succeeded"], partially_succeeded: ["部分完成", "partial"], needs_confirmation: ["等待确认", "partial"], failed: ["执行失败", "failed"], }; const [label, state] = submittedUnverified && normalizedStatus === "succeeded" ? ["指令已提交 · 等待状态确认", "partial"] : statusMap[normalizedStatus] || statusMap.idle; const badge = $("#v2TaskState"); if (badge) { badge.textContent = label; badge.dataset.state = state; } $$(".mode-chips [data-mode-command]").forEach((button) => { const active = Boolean(utterance) && ( utterance === button.dataset.modeCommand || utterance.includes(button.textContent.trim()) ); button.classList.toggle("v2-sync-active", active); }); renderTaskTimeline(task); if (normalizedStatus === "running") { // The streaming endpoint can publish fully validated commands before the // slower display-sync poll observes the terminal execution state. A stale // `running` snapshot for the same task must not erase those commands. if (!validatedCommandsBelongToTask(task)) { resetValidatedCommandContext(); renderParsedCanCommands(null, { message: "正在解析自然语言并生成控制指令…" }); } } else if (result) { const commands = parsedCanCommands(result); if (commands.length) { validatedCommandsVisible = true; rememberValidatedCommandContext(result); renderParsedCanCommands(result); } else if (!validatedCommandsBelongToTask(task)) { resetValidatedCommandContext(); renderParsedCanCommands(result); } } } function applyDisplaySnapshot(snapshot) { const synchronizedCatalog = Array.isArray(snapshot?.extension?.catalog) ? snapshot.extension.catalog : []; const revision = Number(snapshot?.revision ?? -1); if (revision !== displaySync.revision) { displaySync.revision = revision; const task = snapshot?.task || {}; 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 .map((item) => `${item.device_id || "external"}:${item.action || item.name || "capability"}`) .sort() .join("|"); if (extensionFingerprint && extensionFingerprint !== displaySync.extensionFingerprint) { displaySync.extensionFingerprint = extensionFingerprint; appendSyncLog({ key: `extension:${extensionFingerprint}`, category: "extension", title: "外设能力目录已同步", detail: `${new Set(synchronizedCatalog.map((item) => item.device_id || "external")).size} 个外设 · ${synchronizedCatalog.length} 项能力`, status: "success", }); } window.dispatchEvent(new CustomEvent("cognitive-agent:display-sync", { detail: snapshot })); } async function pollVehicleDisplaySync() { try { const response = await fetch("/api/vehicle-display-sync", { credentials: "same-origin", cache: "no-store", headers: { Accept: "application/json" }, }); if (!response.ok) throw new Error(`HTTP ${response.status}`); applyDisplaySnapshot(await response.json()); appendSyncLog({ key: "sync:online", category: "sync", title: "网页同步链路已建立", detail: "车机任务与设备状态每 2 秒同步", status: "success" }); } catch (_error) { console.warn("[vehicle-display-sync]", _error); } } function enableRemoteOrchestration() { const form = $("#requestForm"); const input = $("#utterance"); if (input) { input.readOnly = false; input.removeAttribute("aria-readonly"); input.setAttribute("aria-label", "输入问题或座舱控制需求"); input.placeholder = "输入问题或座舱控制需求"; } form?.addEventListener("submit", () => { const utterance = input?.value.trim(); if (utterance) appendSyncLog({ category: "task", title: "网页端发送助手请求", detail: utterance, status: "info", }); }); $$("[data-mode-command]").forEach((button) => { button.removeAttribute("aria-disabled"); button.title = "点击后由网页端发送至车机执行"; }); } function syncSidebarLogBaseline() { const sidebar = $(".v2-sidebar"); const lowerPanel = $("#v2DevicePanel") || $(".joint-twin-shell > .twin-band"); if (!sidebar || !lowerPanel || ui.sidebarCollapsed) return; const sidebarRect = sidebar.getBoundingClientRect(); const overview = $(".v2-sync-sidebar-overview", sidebar); const overviewRect = overview?.getBoundingClientRect(); const overviewBottom = overview && overviewRect ? Array.from(overview.children).reduce( (bottom, child) => Math.max(bottom, child.getBoundingClientRect().bottom), overviewRect.top, ) : 0; const overviewPaddingBottom = overview ? Number.parseFloat(getComputedStyle(overview).paddingBottom) || 0 : 0; const requiredOverviewHeight = overviewRect ? Math.ceil(overviewBottom - overviewRect.top + overviewPaddingBottom + 10) : 0; const offset = Math.max( 0, lowerPanel.getBoundingClientRect().top - sidebarRect.top, requiredOverviewHeight, ); sidebar.style.setProperty("--v2-sync-log-top", `${offset}px`); } function buildSyncControlLayer() { document.body.classList.add("v2-passive-display", "v2-interactive-sync"); document.body.dataset.module = "self-orchestration"; enableRemoteOrchestration(); const sidebar = $(".v2-sidebar"); const sidebarFooter = $(".v2-sidebar-footer", sidebar); const syncSidebar = make("section", "v2-sync-sidebar", `

    运行日志 / 同步记录

      `); if (sidebar && sidebarFooter) { sidebar.insertBefore(syncSidebar, sidebarFooter); const syncOverview = $(".v2-sync-sidebar-overview", syncSidebar); const transportField = $("#vehicleTransportField"); const executionField = $(".field-group:has(.execution-mode-options)"); const simulationFaultField = $("#simulationFaultField"); if (syncOverview) { const insertVisibleControl = (field, className) => { if (!field) return; field.classList.add(className); syncOverview.append(field); }; insertVisibleControl(transportField, "v2-sync-transport-field"); if (executionField) executionField.hidden = false; insertVisibleControl(executionField, "v2-sync-execution-field"); insertVisibleControl(simulationFaultField, "v2-sync-simulation-fault-field"); const canCommandPanel = make("details", "v2-can-command-panel", ` 下发控制指令 0

      等待准备下发的控制指令

      `); canCommandPanel.id = "v2CanCommandPanel"; syncOverview.append(canCommandPanel); canCommandPanel.addEventListener("toggle", () => { requestAnimationFrame(syncSidebarLogBaseline); }); } syncOverview?.append(sidebarFooter); } const naturalPanel = $("#v2NaturalPanel"); const taskOverview = make("section", "v2-task-overview", `
      当前车机任务等待车机端或网页端选择预设模式、输入一句话指令
      等待任务
      01等待车机或网页端下发编排任务
      `); naturalPanel?.append(taskOverview); const jointShell = $(".joint-twin-shell"); const cabinBand = $(".cabin-band", jointShell); if (jointShell && cabinBand) { const rightRail = make("aside", "v2-unified-right-rail", `

      车端设备状态

      实时反馈
      `); jointShell.insertBefore(rightRail, cabinBand); const panelDock = $("#v2PanelControlDock"); if (panelDock) { const deviceToggle = make("button", "v2-panel-control-button", '车端设备状态'); deviceToggle.type = "button"; deviceToggle.dataset.panelToggle = "devices"; deviceToggle.setAttribute("aria-controls", "v2DevicePanel"); deviceToggle.setAttribute("aria-pressed", "false"); deviceToggle.setAttribute("aria-expanded", "false"); deviceToggle.setAttribute("aria-label", "展开车端设备状态"); deviceToggle.title = "展开车端设备状态"; const extensionToggle = make("button", "v2-panel-control-button", '外设与扩展能力'); extensionToggle.type = "button"; extensionToggle.dataset.panelToggle = "extensions"; extensionToggle.setAttribute("aria-controls", "v2ExtensionPanel"); extensionToggle.setAttribute("aria-pressed", "false"); extensionToggle.setAttribute("aria-expanded", "false"); extensionToggle.setAttribute("aria-label", "展开外设与扩展能力"); extensionToggle.title = "展开外设与扩展能力"; panelDock.append(deviceToggle, extensionToggle); } buildTwinDeviceInteractions(); cabinBand.classList.add("v2-source-state-only"); new MutationObserver(scheduleSyncedDeviceRender).observe(cabinBand, { attributes: true, childList: true, characterData: true, subtree: true, }); const stateBand = $(".state-band"); if (stateBand) new MutationObserver(scheduleSyncedDeviceRender).observe(stateBand, { attributes: true, childList: true, characterData: true, subtree: true, }); } setSidePanelVisibility("devices", false); setSidePanelVisibility("extensions", false); window.addEventListener("resize", syncSidebarLogBaseline); requestAnimationFrame(syncSidebarLogBaseline); document.addEventListener("click", (event) => { const toggle = event.target.closest("[data-panel-toggle], [data-panel-close]"); if (toggle) { const key = toggle.dataset.panelToggle || toggle.dataset.panelClose; setSidePanelVisibility(key, !panelVisible(key)); } }); const deviceGrid = $("#v2DeviceSyncGrid"); deviceGrid?.addEventListener("click", (event) => { const controlToggle = event.target.closest("[data-device-control-toggle]"); if (controlToggle) { const deviceId = controlToggle.dataset.deviceControlToggle; displaySync.controlDevice = displaySync.controlDevice === deviceId ? null : deviceId; if (displaySync.controlDevice) presentTwinDevice(deviceId, { pinned: true }); else if (displaySync.pinnedDevice === deviceId) clearTwinDevicePreview({ force: true }); renderSyncedDevices(); return; } const apply = event.target.closest("[data-device-control-apply]"); if (apply) { sendDeviceControl(apply.dataset.deviceControlApply); return; } const focus = event.target.closest("[data-device-focus]"); if (focus) { const samePinned = displaySync.pinnedDevice === focus.dataset.deviceFocus; if (samePinned) clearTwinDevicePreview({ force: true }); else presentTwinDevice(focus.dataset.deviceFocus, { pinned: true }); } }); deviceGrid?.addEventListener("input", (event) => { if (event.target.matches("[data-device-control]")) storeDeviceControlDraft(event.target); }); deviceGrid?.addEventListener("change", (event) => { if (event.target.matches("select[data-device-control]")) storeDeviceControlDraft(event.target); }); $("#v2LogFilter")?.addEventListener("change", renderSyncLogs); const beginPendingControlRequest = (request = {}) => { beginCommandTiming(request); resetValidatedCommandContext(); 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) => { const result = event.detail || {}; const submittedUnverified = result.execution_outcome === "submitted_unverified" || result.execution_report?.spec?.verification_status === "submitted_unverified"; const commands = parsedCanCommands(result); const matchesTimedRequest = commandResultMatchesTimedRequest(result); if (matchesTimedRequest && commands.length) { validatedCommandsVisible = true; rememberValidatedCommandContext(result); renderParsedCanCommands(result, { recordTiming: commandTimingState.displayedAt == null, reveal: commandTimingState.requestStartedAt != null, }); } else if (matchesTimedRequest && !validatedCommandsBelongToTask({ utterance: result.utterance, result, })) { resetValidatedCommandContext(); renderParsedCanCommands(result); } appendSyncLog({ key: result.request_id ? `result:${result.request_id}` : "", category: "task", title: submittedUnverified ? "CAN 指令已提交,等待车辆状态确认" : ["succeeded", "partially_succeeded"].includes(result.status) ? "编排任务执行完成" : "编排任务返回结果", detail: `${result.summary || result.message || "执行反馈已同步"} · ${submittedUnverified ? "submitted_unverified" : result.status || "unknown"}`, status: submittedUnverified ? "info" : ["succeeded", "partially_succeeded"].includes(result.status) ? "success" : result.status === "failed" ? "error" : "info", }); }); window.addEventListener("cognitive-agent:request-complete", (event) => { 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) { const error = event.detail.error; const outcomeLabels = { failed_or_blocked: "失败或已阻止", submitted_unverified: "已提交但未验证", verified_success: "反馈验证成功", }; const outcome = outcomeLabels[error.outcome]; appendSyncLog({ category: "task", title: "编排任务执行失败", detail: `${error.detail || error.message || "请求失败"}${outcome ? ` · ${outcome}` : ""}`, status: "error", }); } }); window.addEventListener("cognitive-agent:connection", (event) => { const online = event.detail?.status === "online"; appendSyncLog({ key: `connection:${event.detail?.status}`, category: "sync", title: online ? "车机服务连接正常" : "车机服务连接异常", detail: online ? "远程控制与状态同步通道可用" : String(event.detail?.status || "offline"), status: online ? "success" : "error", }); }); renderSyncedDevices(); setTaskPresentation({ status: "idle" }); appendSyncLog({ key: "client:ready", category: "sync", title: "同步控制台已就绪", detail: "正在连接车机任务与设备状态服务", status: "info" }); pollVehicleDisplaySync(); displaySync.pollTimer = window.setInterval(pollVehicleDisplaySync, 2000); const updateClock = () => { if ($("#v2SyncClock")) $("#v2SyncClock").textContent = new Intl.DateTimeFormat("zh-CN", { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false, }).format(new Date()); }; updateClock(); window.setInterval(updateClock, 1000); } function setPage(pageIndex) { ui.activePage = 0; const definition = pageDefinitions[ui.activePage]; $(".v2-output-shell")?.classList.toggle("v2-twin-active", definition.index === 0); $$(".v2-output-page").forEach((page) => { const active = Number(page.dataset.v2Page) === ui.activePage; page.classList.toggle("v2-active-output", active); page.setAttribute("aria-hidden", active ? "false" : "true"); }); $$(".v2-page-button").forEach((button) => { const active = Number(button.dataset.v2PageTarget) === ui.activePage; button.classList.toggle("active", active); button.setAttribute("aria-current", active ? "page" : "false"); }); $("#v2PageIndex").textContent = String(definition.index).padStart(2, "0"); $("#v2PageTitle").textContent = definition.label; $("#v2OutputState").textContent = definition.index === 0 ? "LIVE STATE" : "AUDIT VIEW"; requestAnimationFrame(updateScrollCues); } function updateScrollCues() { $$(".result-workspace .tab-panel").forEach((panel) => { const active = panel.classList.contains("v2-active-output"); panel.classList.toggle( "v2-scrollable", active && panel.scrollHeight > panel.clientHeight + 6, ); }); } function updateModeEdgeHints() { const chips = $(".v2-mode-rail .mode-chips"); if (!chips) return; const buttons = $$('button', chips); buttons.forEach((button) => { button.classList.remove("v2-edge-fade-left", "v2-edge-fade-right"); button.style.removeProperty("--v2-edge-fade-start"); button.style.removeProperty("--v2-edge-fade-end"); }); if (buttons.length < 2 || chips.clientWidth <= 0) return; const viewport = chips.getBoundingClientRect(); const visible = buttons.map((button) => ({ button, rect: button.getBoundingClientRect(), })).filter(({ rect }) => { return rect.right > viewport.left + 1 && rect.left < viewport.right - 1; }); if (!visible.length) return; const fadeSpan = 30; const firstVisible = visible[0]; const lastVisible = visible[visible.length - 1]; if (firstVisible.rect.left < viewport.left - 1) { const clippedAt = Math.min(100, Math.max( 0, ((viewport.left - firstVisible.rect.left) / firstVisible.rect.width) * 100, )); firstVisible.button.style.setProperty("--v2-edge-fade-start", `${clippedAt}%`); firstVisible.button.style.setProperty( "--v2-edge-fade-end", `${Math.min(100, clippedAt + fadeSpan)}%`, ); firstVisible.button.classList.add("v2-edge-fade-left"); } if (lastVisible.rect.right > viewport.right + 1) { const clippedAt = Math.min(100, Math.max( 0, ((viewport.right - lastVisible.rect.left) / lastVisible.rect.width) * 100, )); lastVisible.button.style.setProperty( "--v2-edge-fade-start", `${Math.max(0, clippedAt - fadeSpan)}%`, ); lastVisible.button.style.setProperty("--v2-edge-fade-end", `${clippedAt}%`); lastVisible.button.classList.add("v2-edge-fade-right"); } } function setControlMode(mode, { triggerGesture = true } = {}) { ui.controlMode = mode === "gesture" ? "gesture" : "natural"; sessionStorage.setItem("cognitive-agent.control-mode", ui.controlMode); document.body.dataset.controlMode = ui.controlMode; const requestStatus = $(".v2-request-status"); const statusTarget = ui.controlMode === "gesture" ? $("#v2GestureExecutionSlot") : $("#v2NaturalGrid"); if (requestStatus && statusTarget && requestStatus.parentElement !== statusTarget) { statusTarget.append(requestStatus); } const drawer = $("#gestureDrawer"); if (!triggerGesture) { const gestureVisible = ui.controlMode === "gesture"; drawer?.setAttribute("aria-hidden", String(!gestureVisible)); drawer?.classList.toggle("open", gestureVisible); return; } if (ui.controlMode === "gesture") { window.CognitiveAgentGesture?.open?.(); } else if (drawer?.classList.contains("open")) { window.CognitiveAgentGesture?.close?.(); } } function setModule(module, { persist = true } = {}) { const allowed = new Set(["self-orchestration", "self-extension"]); ui.activeModule = allowed.has(module) ? module : "self-orchestration"; if (persist) { sessionStorage.setItem("cognitive-agent.active-module", ui.activeModule); } document.body.dataset.module = ui.activeModule; $$('.v2-module-tab').forEach((button) => { const active = button.dataset.module === ui.activeModule; button.classList.toggle("active", active); button.setAttribute("aria-selected", String(active)); button.tabIndex = active ? 0 : -1; }); if (ui.activeModule === "self-extension") { const drawer = $("#gestureDrawer"); if (drawer?.classList.contains("open")) $("#gestureCloseButton")?.click(); } else { setControlMode(ui.controlMode, { triggerGesture: false }); } window.dispatchEvent(new CustomEvent("cognitive-agent:module-change", { detail: { module: ui.activeModule }, })); window.setTimeout(() => window.dispatchEvent(new Event("resize")), 120); } function setSidebarCollapsed(collapsed) { ui.sidebarCollapsed = Boolean(collapsed); sessionStorage.setItem( "cognitive-agent.sidebar-collapsed", String(ui.sidebarCollapsed), ); document.body.classList.toggle("v2-sidebar-collapsed", ui.sidebarCollapsed); const sidebar = $("#v2SidebarPanel"); if (sidebar) { sidebar.setAttribute("aria-hidden", String(ui.sidebarCollapsed)); sidebar.toggleAttribute("inert", ui.sidebarCollapsed); } const button = $("#v2SidebarToggle"); if (button) { const expanded = !ui.sidebarCollapsed; button.classList.toggle("active", expanded); button.setAttribute("aria-pressed", String(expanded)); button.setAttribute("aria-expanded", String(!ui.sidebarCollapsed)); const label = ui.sidebarCollapsed ? "展开左侧控制台" : "收起左侧控制台"; button.setAttribute("aria-label", label); button.title = label; button.innerHTML = '左侧控制台'; } window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } }); window.setTimeout(() => window.dispatchEvent(new Event("resize")), 180); } function keepRunButtonAvailable() { const runButton = $("#runButton"); if (runButton && !runButton.classList.contains("loading")) runButton.disabled = false; } function syncRuntimePresentation(connectionState = window.CognitiveAgentNetwork?.getState?.()) { const connectionText = $("#v2ConnectionText"); if (connectionText) { const status = connectionState?.status || "connecting"; const labels = { connecting: "正在连接服务", online: "服务已连接", degraded: "连接不稳定", offline: "服务已断开", }; connectionText.textContent = labels[status] || labels.connecting; connectionText.title = connectionState?.lastError || connectionText.textContent; $(".v2-sidebar-footer")?.classList.toggle( "offline", status === "offline" || status === "degraded", ); } keepRunButtonAvailable(); } function bindEvents() { $$('.v2-module-tab').forEach((button) => { button.addEventListener("click", () => { if (!button.disabled) setModule(button.dataset.module); }); }); $$(".v2-page-button").forEach((button) => { button.addEventListener("click", () => setPage(button.dataset.v2PageTarget)); }); $("#v2LogoutButton")?.addEventListener( "click", () => window.CognitiveAgentConsole?.logout?.(), ); $("#v2SidebarToggle")?.addEventListener( "click", () => setSidebarCollapsed(!ui.sidebarCollapsed), ); document.addEventListener("click", () => { closeReasoningPicker(); }); document.addEventListener("keydown", (event) => { if (event.key === "Escape") { closeReasoningPicker(); clearTwinDevicePreview({ force: true }); } }); window.addEventListener("resize", updateScrollCues); window.addEventListener("cognitive-agent:result", () => requestAnimationFrame(updateScrollCues)); window.addEventListener("cognitive-agent:connection", (event) => { syncRuntimePresentation(event.detail); }); window.addEventListener("cognitive-agent:vehicle-feedback-state", () => { renderSyncedDevices(); if (displaySync.activeDevice) { presentTwinDevice(displaySync.activeDevice, { pinned: Boolean(displaySync.pinnedDevice), selectSeat: false, }); } }); const observer = new MutationObserver(() => keepRunButtonAvailable()); [$("#hardwareMode"), $("#simulationMode")] .filter(Boolean) .forEach((node) => observer.observe(node, { attributes: true, childList: true, subtree: true, characterData: true, })); } function init() { const workspace = $(".workspace"); const commandPanel = $(".command-panel"); const mainPanel = $(".main-panel"); const originalForm = $("#requestForm"); if (!workspace || !commandPanel || !mainPanel || !originalForm) return; document.body.classList.add("console-v2"); const panelHeading = $(".panel-heading", commandPanel); const modeRail = $(".mode-rail", commandPanel); const requestHeader = $(".request-header", mainPanel); const gestureDrawer = $("#gestureDrawer"); const detached = { panelHeading, modeRail, requestHeader, gestureDrawer, originalForm, }; Object.values(detached).filter(Boolean).forEach((node) => node.remove()); buildSidebar(commandPanel); prepareRuntimeControls(originalForm); buildOperation( mainPanel, originalForm, panelHeading, requestHeader, gestureDrawer, ); buildModuleSwitcher(mainPanel, modeRail); buildOutput(mainPanel, modeRail); buildSyncControlLayer(); bindEvents(); setControlMode(ui.controlMode, { triggerGesture: false }); setModule(ui.activeModule, { persist: false }); setSidebarCollapsed(ui.sidebarCollapsed); setPage(0); syncRuntimePresentation(); window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } }); requestAnimationFrame(() => { document.body.classList.add("v2-ready"); window.setTimeout(() => $("#v2BootScreen")?.remove(), 220); }); } window.CognitiveAgentConsoleV2 = { getModule: () => ui.activeModule, getRole: () => "user", getParsedCanCommands: (result) => parsedCanCommands(result).map((item) => ({ ...item })), positionTwinDevicePopover, setModule, }; document.addEventListener("DOMContentLoaded", init); }());