优化的前端UI
This commit is contained in:
@@ -618,12 +618,8 @@
|
||||
const naturalPanel = $("#v2NaturalPanel");
|
||||
if (panelHeading) {
|
||||
panelHeading.classList.add("v2-operation-heading", "v2-module-heading");
|
||||
const kicker = $(".section-kicker", panelHeading);
|
||||
const title = $("h2", panelHeading);
|
||||
const intro = $(".panel-intro", panelHeading);
|
||||
if (kicker) kicker.textContent = "REMOTE ORCHESTRATION · LIVE SYNC";
|
||||
if (title) title.textContent = "一句话远程编排";
|
||||
if (intro) intro.textContent = "网页指令将通过同步链路发送至车机;车机任务与执行反馈在右侧实时更新";
|
||||
naturalPanel.append(panelHeading);
|
||||
}
|
||||
|
||||
@@ -643,9 +639,7 @@
|
||||
|
||||
const gesturePanel = $("#v2GesturePanel");
|
||||
const gestureHeading = make("header", "v2-operation-heading v2-module-heading v2-gesture-operation-heading", `
|
||||
<span class="section-kicker">SELF-ORCHESTRATION</span>
|
||||
<h2>抬抬手,控制整车</h2>
|
||||
<p class="panel-intro">通过摄像头识别手势,直观控制车舱功能</p>`);
|
||||
<h2>抬抬手,控制整车</h2>`);
|
||||
gesturePanel.append(gestureHeading);
|
||||
if (gestureDrawer) {
|
||||
gestureDrawer.classList.add("v2-inline-gesture");
|
||||
@@ -693,7 +687,7 @@
|
||||
switcher.innerHTML = `
|
||||
<div class="v2-unified-brand">
|
||||
<span class="v2-unified-brand-mark"><i data-lucide="waypoints"></i></span>
|
||||
<div><small>COCKPIT REMOTE · LIVE MIRROR</small><h1>511工程-智能座舱</h1></div>
|
||||
<div><h1>511工程-智能座舱</h1></div>
|
||||
</div>
|
||||
<div class="v2-remote-control-state"><i data-lucide="radio-tower"></i><span>网页控制链路</span><strong>已启用</strong></div>
|
||||
<div class="v2-unified-live"><i></i><span>LIVE</span><b id="v2SyncClock">--:--:--</b></div>`;
|
||||
@@ -706,7 +700,7 @@
|
||||
<header class="v2-output-heading">
|
||||
<div>
|
||||
<span class="v2-page-index" id="v2PageIndex">00</span>
|
||||
<div><small>INFORMATION OUTPUT</small><h2 id="v2PageTitle">整车联合数字孪生</h2></div>
|
||||
<div><h2 id="v2PageTitle">整车联合数字孪生</h2></div>
|
||||
</div>
|
||||
<span class="v2-output-state"><i></i><span id="v2OutputState">LIVE STATE</span></span>
|
||||
</header>
|
||||
@@ -1168,7 +1162,7 @@
|
||||
popover.dataset.device = deviceId;
|
||||
popover.dataset.pinned = String(Boolean(displaySync.pinnedDevice));
|
||||
popover.innerHTML = `
|
||||
<header><span><i data-lucide="${device.icon}"></i></span><div><small>LIVE DEVICE FEEDBACK</small><strong>${escapeHtml(device.name)}</strong></div><b>${pinned ? "已固定" : "靠近查看"}</b><button class="v2-twin-popover-close" type="button" data-twin-popover-close aria-label="关闭${escapeHtml(device.name)}状态详情" title="关闭"><i data-lucide="x"></i></button></header>
|
||||
<header><span><i data-lucide="${device.icon}"></i></span><div><strong>${escapeHtml(device.name)}</strong></div><b>${pinned ? "已固定" : "靠近查看"}</b><button class="v2-twin-popover-close" type="button" data-twin-popover-close aria-label="关闭${escapeHtml(device.name)}状态详情" title="关闭"><i data-lucide="x"></i></button></header>
|
||||
<dl>${rows.map(([label, value]) => `<div><dt>${escapeHtml(label)}</dt><dd>${escapeHtml(value)}</dd></div>`).join("")}</dl>`;
|
||||
$$("[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));
|
||||
@@ -1493,7 +1487,7 @@
|
||||
const sidebarFooter = $(".v2-sidebar-footer", sidebar);
|
||||
const syncSidebar = make("section", "v2-sync-sidebar", `
|
||||
<div class="v2-sync-sidebar-overview">
|
||||
<div class="v2-sync-sidebar-heading"><small>REMOTE CLIENT</small><h2>511工程-智能座舱</h2><p>网页可远程编排,车机任务、设备反馈与 USB 外设能力保持同步。</p></div>
|
||||
<div class="v2-sync-sidebar-heading"><h2>511工程-智能座舱</h2></div>
|
||||
<div class="v2-sync-link-stack">
|
||||
<div class="v2-sync-link" id="v2HeadUnitState" data-state="checking"><i data-lucide="monitor-smartphone"></i><div><strong>车机链路</strong><span>连接检查中</span></div><b></b></div>
|
||||
<div class="v2-sync-link" id="v2MirrorState" data-state="checking"><i data-lucide="refresh-cw"></i><div><strong>网页同步</strong><span>建立同步中</span></div><b></b></div>
|
||||
@@ -1501,7 +1495,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<section class="v2-sync-log" aria-labelledby="v2SyncLogTitle">
|
||||
<header><div><small>RUNTIME / SYNC</small><h3 id="v2SyncLogTitle">运行日志 / 同步记录</h3></div><select id="v2LogFilter" aria-label="筛选同步日志"><option value="all">全部</option><option value="task">任务</option><option value="sync">同步</option><option value="extension">外设</option><option value="device">设备</option></select></header>
|
||||
<header><div><h3 id="v2SyncLogTitle">运行日志 / 同步记录</h3></div><select id="v2LogFilter" aria-label="筛选同步日志"><option value="all">全部</option><option value="task">任务</option><option value="sync">同步</option><option value="extension">外设</option><option value="device">设备</option></select></header>
|
||||
<ol id="v2SyncLogList" aria-live="polite"></ol>
|
||||
</section>`);
|
||||
if (sidebar && sidebarFooter) {
|
||||
@@ -1555,7 +1549,7 @@
|
||||
if (jointShell && cabinBand) {
|
||||
const rightRail = make("aside", "v2-unified-right-rail", `
|
||||
<section class="v2-device-sync-board v2-side-status-panel" id="v2DevicePanel">
|
||||
<header><div><small>LIVE VEHICLE DEVICES</small><h3>车端设备状态</h3></div><span><i></i>实时反馈</span><button type="button" data-panel-toggle="devices" aria-label="收起车端设备状态"><i data-lucide="panel-left-close"></i><span data-panel-action-label>收起</span></button></header>
|
||||
<header><div><h3>车端设备状态</h3></div><span><i></i>实时反馈</span><button type="button" data-panel-toggle="devices" aria-label="收起车端设备状态"><i data-lucide="panel-left-close"></i><span data-panel-action-label>收起</span></button></header>
|
||||
<div class="v2-device-sync-grid" id="v2DeviceSyncGrid"></div>
|
||||
</section>
|
||||
<section class="v2-extension-panel v2-side-status-panel" id="v2ExtensionPanel"><div class="v2-extension-sync-mount" id="vehicleExtensionSync" aria-live="polite"></div></section>`);
|
||||
|
||||
+14
-27
@@ -10,20 +10,20 @@
|
||||
<link rel="icon" href="/icons/app-icon-192.png" />
|
||||
<title>511工程-智能座舱</title>
|
||||
<link rel="stylesheet" href="/styles.css?v=dual-seat-16" />
|
||||
<link rel="stylesheet" href="/console-v2.css?v=delivery-state-43" />
|
||||
<link rel="stylesheet" href="/self-extension.css?v=gscu-tcp-39" />
|
||||
<link rel="stylesheet" href="/console-v2.css?v=delivery-state-44" />
|
||||
<link rel="stylesheet" href="/self-extension.css?v=gscu-tcp-40" />
|
||||
<link rel="stylesheet" href="/speech-input.css?v=sensevoice-4" />
|
||||
<script src="/vendor/lucide/0.468.0/lucide.min.js" defer></script>
|
||||
<script src="/vendor/three/0.147.0/three.min.js" defer></script>
|
||||
<script src="/vendor/three/0.147.0/fflate.min.js" defer></script>
|
||||
<script src="/vendor/three/0.147.0/FBXLoader.js" defer></script>
|
||||
<script src="/network.js?v=android-pad-2" defer></script>
|
||||
<script src="/app.js?v=dual-seat-109" defer></script>
|
||||
<script src="/app.js?v=dual-seat-110" defer></script>
|
||||
<script src="/speech-input.js?v=sensevoice-6" defer></script>
|
||||
<script type="module" src="/gesture-control.js?v=android-pad-9"></script>
|
||||
<script src="/pwa.js?v=android-pad-3" defer></script>
|
||||
<script src="/console-v2.js?v=delivery-state-50" defer></script>
|
||||
<script src="/self-extension.js?v=gscu-tcp-40" defer></script>
|
||||
<script src="/console-v2.js?v=delivery-state-51" defer></script>
|
||||
<script src="/self-extension.js?v=gscu-tcp-41" defer></script>
|
||||
</head>
|
||||
<body class="console-v2">
|
||||
<div class="v2-boot-screen" id="v2BootScreen" role="status" aria-live="polite">
|
||||
@@ -34,14 +34,12 @@
|
||||
<div class="workspace">
|
||||
<aside class="command-panel">
|
||||
<div class="panel-heading">
|
||||
<span class="section-kicker">NATURAL CABIN CONTROL</span>
|
||||
<h2>说一句,就调好了</h2>
|
||||
<p class="panel-intro">座椅、空调、天幕与氛围灯,一句话联动完成。</p>
|
||||
</div>
|
||||
|
||||
<div class="mode-rail" aria-label="快捷功能模式">
|
||||
<div class="mode-rail-heading">
|
||||
<div><span>功能模式</span><small>点击模式即可执行,运行状态会实时反馈</small></div>
|
||||
<div><span>功能模式</span></div>
|
||||
<button id="saveModeToggle" type="button"><i data-lucide="bookmark-plus"></i>根据指令创建</button>
|
||||
</div>
|
||||
<div class="mode-chips" aria-label="快捷功能模式" aria-live="polite">
|
||||
@@ -178,7 +176,6 @@
|
||||
<main class="main-panel">
|
||||
<section class="request-header">
|
||||
<div>
|
||||
<span class="section-kicker">ORCHESTRATION RUN</span>
|
||||
<div class="request-title-row">
|
||||
<h2 id="resultTitle">等待请求</h2>
|
||||
<span class="run-status idle" id="runStatus">IDLE</span>
|
||||
@@ -194,9 +191,7 @@
|
||||
<section class="cabin-band" aria-labelledby="cabinStateTitle">
|
||||
<div class="section-title-row cabin-heading">
|
||||
<div class="cabin-title-copy">
|
||||
<span class="section-kicker">LIVE VEHICLE EXECUTORS</span>
|
||||
<h3 id="cabinStateTitle">整车设备状态</h3>
|
||||
<p>CH0 独立控制副驾四电机,CH1 映射主驾与车身执行器;所有状态按当前反馈源更新。</p>
|
||||
</div>
|
||||
<div class="cabin-channel-legend" aria-label="整车设备通道说明">
|
||||
<span data-channel="ch0"><i>CH0</i>副驾座椅</span>
|
||||
@@ -337,7 +332,6 @@
|
||||
<section class="twin-band" aria-labelledby="twinTitle">
|
||||
<div class="twin-heading">
|
||||
<div class="twin-title-copy">
|
||||
<span class="section-kicker">LIVE VEHICLE DIGITAL TWIN</span>
|
||||
<h3 id="twinTitle">整车孪生界面</h3>
|
||||
<div class="preset-indicator" id="presetIndicator" hidden>
|
||||
<i data-lucide="bookmark-check"></i><span></span>
|
||||
@@ -401,7 +395,7 @@
|
||||
<section class="state-band">
|
||||
<div class="seat-state-section">
|
||||
<div class="section-title-row">
|
||||
<div><span class="section-kicker">ACTUATOR STATE</span><h3>前排座椅状态</h3></div>
|
||||
<div><h3>前排座椅状态</h3></div>
|
||||
<span class="simulation-label" id="seatFeedbackModeLabel">CH0 / CH1 FEEDBACK</span>
|
||||
</div>
|
||||
<div class="seat-grid">
|
||||
@@ -429,7 +423,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="coverage-section">
|
||||
<span class="section-kicker">MATRIX COVERAGE</span>
|
||||
<h3>当前控制边界</h3>
|
||||
<dl>
|
||||
<div><dt>空调 18–32℃ / 0–7 档</dt><dd class="available">可编译</dd></div>
|
||||
@@ -444,7 +437,7 @@
|
||||
|
||||
<section class="detail-section result-workspace" aria-labelledby="resultWorkspaceTitle">
|
||||
<header class="result-workspace-heading">
|
||||
<div><span class="section-kicker">ORCHESTRATION WORKSPACE</span><h3 id="resultWorkspaceTitle">编排结果工作台</h3><p>四类结果在同一页面连续呈现,也可以通过下方入口快速定位。</p></div>
|
||||
<div><h3 id="resultWorkspaceTitle">编排结果工作台</h3></div>
|
||||
<span class="result-workspace-badge"><i data-lucide="route"></i>REQUEST → CONFIG → CAN → RESULT</span>
|
||||
</header>
|
||||
<nav class="tabs result-nav" aria-label="结果快速导航">
|
||||
@@ -455,7 +448,7 @@
|
||||
</nav>
|
||||
|
||||
<div class="tab-panel active" id="panel-trace" role="tabpanel">
|
||||
<div class="workspace-panel-title"><div><span class="section-kicker">01 · DECISION TRACE</span><h3>决策轨迹与校验</h3><p>从自然语言识别到领域路由,集中查看每一步决策依据。</p></div><span class="workspace-panel-state" id="tracePanelState">IDLE</span></div>
|
||||
<div class="workspace-panel-title"><div><h3>决策轨迹与校验</h3></div><span class="workspace-panel-state" id="tracePanelState">IDLE</span></div>
|
||||
<div class="trace-layout">
|
||||
<div>
|
||||
<div class="detail-heading"><h3>可审计决策摘要</h3><span id="providerUsed">--</span></div>
|
||||
@@ -474,7 +467,7 @@
|
||||
|
||||
<div class="tab-panel config-panel" id="panel-config" role="tabpanel">
|
||||
<div class="config-panel-heading">
|
||||
<div><span class="section-kicker">02 · INSTRUCTION → HARDWARE</span><h3>指令配置与硬件映射</h3><p>每张卡片对应一个控制对象,依次展示目标参数和最终硬件计划。</p></div>
|
||||
<div><h3>指令配置与硬件映射</h3></div>
|
||||
<div class="config-actions"><button class="config-action" type="button" data-copy="config"><i data-lucide="copy"></i>复制 YAML</button><button class="config-action" type="button" data-download="config"><i data-lucide="download"></i>下载配置</button></div>
|
||||
</div>
|
||||
<div class="config-card-grid" id="configCards">
|
||||
@@ -487,7 +480,7 @@
|
||||
</div>
|
||||
|
||||
<div class="tab-panel plan-panel" id="panel-plan" role="tabpanel">
|
||||
<div class="workspace-panel-title plan-panel-heading"><div><span class="section-kicker">03 · CAN COMMAND PLAN</span><h3>CAN 编译计划</h3><p>快速核对帧数量、首帧载荷、反馈帧和执行模式。</p></div><button class="config-action" type="button" data-copy="plan"><i data-lucide="copy"></i>复制计划</button></div>
|
||||
<div class="workspace-panel-title plan-panel-heading"><div><h3>CAN 编译计划</h3></div><button class="config-action" type="button" data-copy="plan"><i data-lucide="copy"></i>复制计划</button></div>
|
||||
<div class="frame-summary" id="frameSummary"><span>Frame ID</span><b>--</b><span>Payload</span><code>--</code><span>Mode</span><b>DRY-RUN</b></div>
|
||||
<details class="config-source-details plan-source-details">
|
||||
<summary><span><i data-lucide="binary"></i>查看原始 CAN 计划</span><small id="planFileName">command-plans/seat.yaml</small></summary>
|
||||
@@ -496,7 +489,7 @@
|
||||
</div>
|
||||
|
||||
<div class="tab-panel result-panel" id="panel-result" role="tabpanel">
|
||||
<div class="workspace-panel-title"><div><span class="section-kicker">04 · EXECUTION RESULT</span><h3>执行结果与硬件回读</h3><p>汇总每个领域的执行状态、CAN 回读和失败原因。</p></div><span class="workspace-panel-state" id="resultPanelState">IDLE</span></div>
|
||||
<div class="workspace-panel-title"><div><h3>执行结果与硬件回读</h3></div><span class="workspace-panel-state" id="resultPanelState">IDLE</span></div>
|
||||
<div class="execution-summary" id="executionSummary">
|
||||
<i data-lucide="circle-dashed"></i><div><strong>尚未执行</strong><span>运行结果将在此处显示</span></div>
|
||||
</div>
|
||||
@@ -509,9 +502,7 @@
|
||||
<aside class="gesture-drawer" id="gestureDrawer" aria-hidden="true" aria-labelledby="gestureDrawerTitle">
|
||||
<header class="gesture-drawer-header">
|
||||
<div>
|
||||
<span class="section-kicker">VISION CONTROL</span>
|
||||
<h2 id="gestureDrawerTitle">手势控制</h2>
|
||||
<p>本机识别,动作通过当前编排链路执行。</p>
|
||||
</div>
|
||||
<button class="gesture-close" id="gestureCloseButton" type="button" aria-label="关闭手势控制"><i data-lucide="x"></i></button>
|
||||
</header>
|
||||
@@ -560,12 +551,10 @@
|
||||
</aside>
|
||||
|
||||
<div class="mode-gallery-backdrop" id="modeGalleryBackdrop" hidden>
|
||||
<section class="mode-gallery" id="modeGallery" role="dialog" aria-modal="true" aria-labelledby="modeGalleryTitle" aria-describedby="modeGalleryDescription">
|
||||
<section class="mode-gallery" id="modeGallery" role="dialog" aria-modal="true" aria-labelledby="modeGalleryTitle">
|
||||
<header class="mode-gallery-header">
|
||||
<div>
|
||||
<span class="mode-gallery-kicker">SCENE LIBRARY</span>
|
||||
<h2 id="modeGalleryTitle">全部功能模式</h2>
|
||||
<p id="modeGalleryDescription">按场景集中展示所有预设及用户创建模式,点击任一模式即可执行。</p>
|
||||
</div>
|
||||
<button class="mode-gallery-close" id="modeGalleryClose" type="button" aria-label="关闭全部功能模式"><i data-lucide="x"></i></button>
|
||||
</header>
|
||||
@@ -576,13 +565,11 @@
|
||||
</div>
|
||||
|
||||
<div class="mode-editor-backdrop" id="modeEditorBackdrop" hidden>
|
||||
<section class="mode-editor" role="dialog" aria-modal="true" aria-labelledby="modeEditorTitle" aria-describedby="modeEditorDescription">
|
||||
<section class="mode-editor" role="dialog" aria-modal="true" aria-labelledby="modeEditorTitle">
|
||||
<div class="mode-editor-setup">
|
||||
<header class="mode-editor-header">
|
||||
<div>
|
||||
<span>MODE WORKFLOW</span>
|
||||
<h2 id="modeEditorTitle">编辑功能模式</h2>
|
||||
<p id="modeEditorDescription">复用主页自然语言解析流程生成控制指令;此步骤不会执行设备。</p>
|
||||
</div>
|
||||
<button id="modeEditorClose" type="button" aria-label="关闭模式编辑器"><i data-lucide="x"></i></button>
|
||||
</header>
|
||||
|
||||
@@ -574,7 +574,6 @@
|
||||
extensionSidebar.className = "v2-sidebar-section v2-extension-sidebar";
|
||||
extensionSidebar.innerHTML = `
|
||||
<div class="v2-section-heading">
|
||||
<span>EXTENSION DESK</span>
|
||||
<h2>控制台</h2>
|
||||
</div>
|
||||
<span class="v2-field-caption">工作模式</span>
|
||||
@@ -612,7 +611,7 @@
|
||||
root.innerHTML = `
|
||||
<section class="v2-extension-view v2-extension-study" data-extension-view="study">
|
||||
<header class="v2-extension-heading">
|
||||
<div class="v2-module-heading"><span class="section-kicker">SELF-EXTENSION</span><h1>外设玩具能力学习</h1><p>添加外设硬件,学习并验证可执行的遥控动作。</p></div>
|
||||
<div class="v2-module-heading"><h1>外设玩具能力学习</h1></div>
|
||||
<div class="v2-extension-heading-actions">
|
||||
<button class="v2-extension-reset-action" type="button" id="extensionResetButton"><i data-lucide="rotate-ccw"></i>重置学习</button>
|
||||
<button type="button" data-add-type="light"><i data-lucide="plus"></i>添加玩具</button>
|
||||
@@ -622,7 +621,7 @@
|
||||
</section>
|
||||
<section class="v2-extension-view v2-extension-control" data-extension-view="control" hidden>
|
||||
<header class="v2-extension-heading">
|
||||
<div class="v2-module-heading"><span class="section-kicker">SELF-EXTENSION</span><h1>外设玩具与车端联动</h1><p>用自然语言统一编排已学习的玩具动作与受控车端设备。</p></div>
|
||||
<div class="v2-module-heading"><h1>外设玩具与车端联动</h1></div>
|
||||
</header>
|
||||
<div class="v2-extension-command-row">
|
||||
<div class="v2-extension-composer">
|
||||
@@ -636,11 +635,11 @@
|
||||
</div>
|
||||
<div class="v2-extension-control-grid">
|
||||
<section class="v2-extension-action-board">
|
||||
<header><div><span class="section-kicker">LEARNED ACTIONS</span><h2>玩具动作</h2></div><strong id="extensionLearnedTotal">0 项可执行</strong></header>
|
||||
<header><div><h2>玩具动作</h2></div><strong id="extensionLearnedTotal">0 项可执行</strong></header>
|
||||
<div class="v2-extension-learned-grid" id="extensionLearnedGrid"></div>
|
||||
</section>
|
||||
<section class="v2-extension-action-board">
|
||||
<header><div><span class="section-kicker">VEHICLE ACTIONS</span><h2>车端设备动作</h2></div><strong>同源安全编排</strong></header>
|
||||
<header><div><h2>车端设备动作</h2></div><strong>同源安全编排</strong></header>
|
||||
<div class="v2-extension-vehicle-grid" id="extensionVehicleGrid"></div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -679,7 +678,7 @@
|
||||
layer.innerHTML = `
|
||||
<dialog class="v2-extension-dialog" id="extensionAddDialog">
|
||||
<form id="extensionAddForm">
|
||||
<header><div><span class="section-kicker">NEW PERIPHERAL</span><h2>添加外设玩具</h2><p>选择类别后会自动建立一组待学习动作。</p></div><button type="button" data-close-extension-dialog="extensionAddDialog" aria-label="关闭"><i data-lucide="x"></i></button></header>
|
||||
<header><div><h2>添加外设玩具</h2></div><button type="button" data-close-extension-dialog="extensionAddDialog" aria-label="关闭"><i data-lucide="x"></i></button></header>
|
||||
<div class="v2-extension-type-chooser" id="extensionTypeChooser"></div>
|
||||
<label><span>玩具名称</span><input id="extensionHardwareName" maxlength="48" autocomplete="off" placeholder="例如:床头灯光玩具" required /></label>
|
||||
<div class="v2-extension-preset-preview"><span>将建立的预设动作</span><div id="extensionPresetPreview"></div></div>
|
||||
@@ -687,7 +686,7 @@
|
||||
</form>
|
||||
</dialog>
|
||||
<dialog class="v2-extension-dialog v2-extension-learn-dialog" id="extensionLearnDialog">
|
||||
<header><div><span class="section-kicker">GSCU1B LEARNING</span><h2 id="extensionLearnTitle">学习遥控动作</h2><p id="extensionLearnPath">选择动作后开始</p></div><button type="button" data-close-extension-dialog="extensionLearnDialog" aria-label="关闭"><i data-lucide="x"></i></button></header>
|
||||
<header><div><h2 id="extensionLearnTitle">学习遥控动作</h2></div><button type="button" data-close-extension-dialog="extensionLearnDialog" aria-label="关闭"><i data-lucide="x"></i></button></header>
|
||||
<div class="v2-extension-learn-steps"><span data-extension-learn-step="1"><b>1</b>配置</span><i></i><span data-extension-learn-step="2"><b>2</b>接收</span><i></i><span data-extension-learn-step="3"><b>3</b>验证</span></div>
|
||||
<section class="v2-extension-learn-page" data-extension-learn-page="1">
|
||||
<div class="v2-extension-learn-target"><span id="extensionLearnIcon"><i data-lucide="radio"></i></span><div><small>目标玩具</small><strong id="extensionLearnDevice">--</strong></div><i data-lucide="arrow-right"></i><div><small>待学习动作</small><strong id="extensionLearnAction">--</strong></div></div>
|
||||
@@ -709,12 +708,12 @@
|
||||
</section>
|
||||
</dialog>
|
||||
<dialog class="v2-extension-dialog v2-extension-reset-dialog" id="extensionResetDialog">
|
||||
<header><div><span class="section-kicker">RESET LEARNING</span><h2>重置学习</h2><p>将擦除学习器并把所有动作恢复为待学习。</p></div><button type="button" data-close-extension-dialog="extensionResetDialog" aria-label="关闭"><i data-lucide="x"></i></button></header>
|
||||
<header><div><h2>重置学习</h2></div><button type="button" data-close-extension-dialog="extensionResetDialog" aria-label="关闭"><i data-lucide="x"></i></button></header>
|
||||
<label><span>输入“清空”确认操作</span><input id="extensionResetConfirm" autocomplete="off" /></label>
|
||||
<button class="v2-extension-danger" type="button" id="extensionConfirmReset" disabled>擦除并重新开始</button>
|
||||
</dialog>
|
||||
<dialog class="v2-extension-dialog v2-gscu-tcp-dialog" id="extensionGscuTcpDialog">
|
||||
<header><div><span class="section-kicker">GSCU1B · TCP SERVER</span><h2>红外学习器 TCP 控制</h2><p>Linux 监听连接;GSCU1B 作为 TCP Client 接入。</p></div><button type="button" data-close-extension-dialog="extensionGscuTcpDialog" aria-label="关闭"><i data-lucide="x"></i></button></header>
|
||||
<header><div><h2>红外学习器 TCP 控制</h2></div><button type="button" data-close-extension-dialog="extensionGscuTcpDialog" aria-label="关闭"><i data-lucide="x"></i></button></header>
|
||||
<section class="v2-gscu-status" id="extensionGscuTcpStatus" data-state="checking"><i data-lucide="radio-tower"></i><div><strong>正在读取连接状态</strong><small>--</small></div><b>CHECK</b></section>
|
||||
<form id="extensionGscuTcpForm" class="v2-gscu-config-form">
|
||||
<label><span>本机监听 IP</span><input id="extensionGscuTcpHost" inputmode="decimal" maxlength="45" placeholder="0.0.0.0" required /><small>填写 0.0.0.0 可监听所有网卡;模块端应填写这台 Linux 的实际局域网 IP。</small></label>
|
||||
@@ -1670,9 +1669,6 @@
|
||||
relearning,
|
||||
};
|
||||
$("#extensionLearnTitle").textContent = `${relearning ? "重新学习" : "学习"}“${capability.name}”`;
|
||||
$("#extensionLearnPath").textContent = relearning
|
||||
? `${type.label} · ${device.name} · 将覆盖 IR ${String(targetSlot).padStart(3, "0")} 原信号`
|
||||
: `${type.label} · ${device.name}`;
|
||||
$("#extensionLearnIcon").innerHTML = `<i data-lucide="${type.icon}"></i>`;
|
||||
$("#extensionLearnDevice").textContent = device.name;
|
||||
$("#extensionLearnAction").textContent = `${capability.name}${relearning ? "(重新学习)" : ""}`;
|
||||
@@ -3330,10 +3326,10 @@
|
||||
|
||||
if (!reader.online) {
|
||||
stage.dataset.state = "offline";
|
||||
body.innerHTML = `<div class="v2-nfc-waiting offline"><span class="v2-nfc-reader-visual"><i data-lucide="scan-line"></i><b></b><b></b></span><div><small>NFC READER</small><strong>读卡器尚未就绪</strong><p>${escapeHtml(nfcReaderNote(reader))}</p></div></div>`;
|
||||
body.innerHTML = `<div class="v2-nfc-waiting offline"><span class="v2-nfc-reader-visual"><i data-lucide="scan-line"></i><b></b><b></b></span><div><strong>读卡器尚未就绪</strong><p>${escapeHtml(nfcReaderNote(reader))}</p></div></div>`;
|
||||
} else if (!card?.uid) {
|
||||
stage.dataset.state = "waiting";
|
||||
body.innerHTML = `<div class="v2-nfc-waiting"><span class="v2-nfc-reader-visual"><i data-lucide="scan-line"></i><b></b><b></b></span><div><small>WAITING FOR NFC</small><strong>请将 NFC 卡靠近读卡器</strong><p>让卡片在读卡区停留约 1 秒;识别后可将 UID 绑定到一个已配置的设备 ID。</p></div></div>`;
|
||||
body.innerHTML = `<div class="v2-nfc-waiting"><span class="v2-nfc-reader-visual"><i data-lucide="scan-line"></i><b></b><b></b></span><div><strong>请将 NFC 卡靠近读卡器</strong><p>让卡片在读卡区停留约 1 秒;识别后可将 UID 绑定到一个已配置的设备 ID。</p></div></div>`;
|
||||
} else if (!linkedDevice) {
|
||||
stage.dataset.state = "new-card";
|
||||
body.innerHTML = `<div class="v2-nfc-detected-heading"><span><i data-lucide="badge-plus"></i></span><div><small>检测到未绑定 NFC</small><strong>${escapeHtml(card.uid)}</strong><p>请选择设备 ID;控制对象名称仅作为调试辅助说明。</p></div></div>
|
||||
@@ -3349,7 +3345,7 @@
|
||||
<div><small>NFC UID · ${escapeHtml(card.uid)}</small><strong>${escapeHtml(linkedDevice.id)}</strong><p>控制对象:${escapeHtml(group?.objectNames.join("、") || linkedDevice.name || "无辅助描述")} · ${enabledCount}/${capabilities.length} 条命令已授权</p></div>
|
||||
<i data-lucide="badge-check"></i>
|
||||
</div>
|
||||
<div class="v2-nfc-capability-heading"><div><small>COMMAND AUTHORIZATION</small><strong>允许此 NFC 执行的命令</strong></div><span>${enabledCount}/${capabilities.length} 已选择</span></div>
|
||||
<div class="v2-nfc-capability-heading"><div><strong>允许此 NFC 执行的命令</strong></div><span>${enabledCount}/${capabilities.length} 已选择</span></div>
|
||||
${capabilities.length ? `<label class="v2-nfc-select-all"><input type="checkbox" data-nfc-select-all ${allEnabled ? "checked" : ""} /><span><strong>全选</strong><small>统一允许或禁止当前设备 ID 下的全部命令</small></span></label><div class="v2-nfc-command-list">${capabilities.map((capability) => `<label class="v2-nfc-command-option"><input type="checkbox" data-nfc-capability-enabled="${escapeHtml(capability.id)}" ${capability.enabled !== false ? "checked" : ""} /><span><strong>${escapeHtml(capability.name)}</strong><small>IR ${String(capability.slot).padStart(3, "0")} · ${escapeHtml((capability.aliases || []).join("、"))}</small></span></label>`).join("")}</div>` : `<div class="v2-nfc-no-devices"><i data-lucide="triangle-alert"></i><div><strong>该设备 ID 暂无有效命令</strong><span>槽位可能已删除或改绑到其他设备 ID,请重新检查红外设置。</span></div></div>`}
|
||||
<div class="v2-nfc-recognized-actions"><span>复选框状态会持久化为自然语言执行白名单。</span><button type="button" data-nfc-unbind>解绑 NFC</button></div>`;
|
||||
}
|
||||
@@ -3361,9 +3357,9 @@
|
||||
const dialog = document.createElement("dialog");
|
||||
dialog.className = "v2-extension-dialog v2-nfc-dialog";
|
||||
dialog.id = "extensionNfcDialog";
|
||||
dialog.innerHTML = `<header><div><span class="section-kicker">NFC · DEVICE AUTHORIZATION</span><h2>NFC 设备授权</h2><p>将 NFC UID 绑定到设备 ID,并限制该卡片可执行的红外命令。</p></div><button type="button" data-close-nfc-dialog aria-label="关闭"><i data-lucide="x"></i></button></header>
|
||||
dialog.innerHTML = `<header><div><h2>NFC 设备授权</h2></div><button type="button" data-close-nfc-dialog aria-label="关闭"><i data-lucide="x"></i></button></header>
|
||||
<section class="v2-nfc-stage" id="extensionNfcStage" data-state="waiting">
|
||||
<header><span><i data-lucide="scan-line"></i></span><div><small>靠近读卡器后</small><h3>NFC 设备绑定</h3><p id="extensionNfcReaderStatus">正在检查读卡器</p></div><b id="extensionNfcReaderBadge" data-state="offline">READER CHECK</b></header>
|
||||
<header><span><i data-lucide="scan-line"></i></span><div><h3>NFC 设备绑定</h3><p id="extensionNfcReaderStatus">正在检查读卡器</p></div><b id="extensionNfcReaderBadge" data-state="offline">READER CHECK</b></header>
|
||||
<div class="v2-nfc-body" id="extensionNfcBody"></div>
|
||||
</section>
|
||||
<footer class="v2-nfc-footnote"><i data-lucide="shield-check"></i><span>NFC UID、设备 ID 与命令授权同步到本机白名单;红外波形仍保存在 GSCU1B 物理槽位。</span></footer>`;
|
||||
@@ -3674,13 +3670,13 @@
|
||||
mount.dataset.hasDevices = String(devices.length > 0);
|
||||
mount.innerHTML = `
|
||||
<header class="v2-extension-sync-heading">
|
||||
<div><small>USB EXTENSION · LIVE</small><h3>外设与扩展能力</h3><button class="v2-extension-nfc-launch" type="button" data-open-nfc-binding><i data-lucide="scan-line"></i><span>NFC 设备绑定</span></button></div>
|
||||
<div><h3>外设与扩展能力</h3><button class="v2-extension-nfc-launch" type="button" data-open-nfc-binding><i data-lucide="scan-line"></i><span>NFC 设备绑定</span></button></div>
|
||||
<span data-state="${devices.length ? "online" : nfcReader.online ? "listening" : "offline"}"><i></i>${statusLabel}</span>
|
||||
<button type="button" data-panel-toggle="extensions" aria-label="${panelCollapsed ? "展开" : "收起"}外设与扩展能力"><i data-lucide="panel-right-close"></i><span data-panel-action-label>${panelCollapsed ? "展开" : "收起"}</span></button>
|
||||
</header>
|
||||
<section class="v2-nfc-inline" data-state="${nfcState}">
|
||||
<span class="v2-nfc-inline-icon"><i data-lucide="${nfcDevice ? "badge-check" : "scan-line"}"></i><b></b></span>
|
||||
<div><small>靠近读卡器后</small><strong>${escapeHtml(nfcTitle)}</strong><p>${escapeHtml(nfcNote)}</p>${nfcCapabilities.length ? `<div>${nfcCapabilities.slice(0, 6).map((capability) => `<i>${escapeHtml(capability.name)}</i>`).join("")}</div>` : ""}</div>
|
||||
<div><strong>${escapeHtml(nfcTitle)}</strong><p>${escapeHtml(nfcNote)}</p>${nfcCapabilities.length ? `<div>${nfcCapabilities.slice(0, 6).map((capability) => `<i>${escapeHtml(capability.name)}</i>`).join("")}</div>` : ""}</div>
|
||||
<button type="button" data-open-nfc-binding>${nfcDevice ? "查看授权" : nfcCard?.uid ? "关联设备" : "开始感应"}</button>
|
||||
</section>
|
||||
<div class="v2-extension-gateway">
|
||||
|
||||
@@ -17,6 +17,9 @@ Provides-Extra: llm
|
||||
Requires-Dist: openai>=1.30; extra == "llm"
|
||||
Provides-Extra: ir
|
||||
Requires-Dist: pyserial<4,>=3.5; extra == "ir"
|
||||
Provides-Extra: speech
|
||||
Requires-Dist: sherpa-onnx==1.13.6; extra == "speech"
|
||||
Requires-Dist: sherpa-onnx-bin==1.13.6; extra == "speech"
|
||||
Provides-Extra: test
|
||||
Requires-Dist: pytest>=8; extra == "test"
|
||||
Requires-Dist: pytest-cov>=5; extra == "test"
|
||||
|
||||
@@ -10,11 +10,14 @@ src/cognitive_agent.egg-info/dependency_links.txt
|
||||
src/cognitive_agent.egg-info/requires.txt
|
||||
src/cognitive_agent.egg-info/top_level.txt
|
||||
src/cognitive_agent/application/capability_registry.py
|
||||
src/cognitive_agent/application/chinese_values.py
|
||||
src/cognitive_agent/application/conversation_state.py
|
||||
src/cognitive_agent/application/direct_can_control.py
|
||||
src/cognitive_agent/application/domain_runtime.py
|
||||
src/cognitive_agent/application/gesture_bridge.py
|
||||
src/cognitive_agent/application/mode_profile.py
|
||||
src/cognitive_agent/application/model_plan_adapter.py
|
||||
src/cognitive_agent/application/mqtt_ota_seat_executor.py
|
||||
src/cognitive_agent/application/native_can_feedback.py
|
||||
src/cognitive_agent/application/native_can_intent.py
|
||||
src/cognitive_agent/application/native_can_relative.py
|
||||
@@ -24,6 +27,7 @@ src/cognitive_agent/application/seat_service.py
|
||||
src/cognitive_agent/application/self_extension.py
|
||||
src/cognitive_agent/application/semantic_ir.py
|
||||
src/cognitive_agent/application/vehicle_profile.py
|
||||
src/cognitive_agent/application/vehicle_state_monitor.py
|
||||
src/cognitive_agent/application/zcan_seat_executor.py
|
||||
src/cognitive_agent/application/calibration/__init__.py
|
||||
src/cognitive_agent/application/calibration/mqtt_client.py
|
||||
@@ -37,11 +41,14 @@ src/cognitive_agent/application/self_repair/healing_model.py
|
||||
src/cognitive_agent/application/self_repair/service.py
|
||||
src/cognitive_agent/application/self_repair/signal_diagnostic.py
|
||||
src/cognitive_agent/application/self_repair/zcan_bridge.py
|
||||
src/cognitive_agent/generated/__init__.py
|
||||
src/cognitive_agent/generated/can_semantic_protocol.py
|
||||
src/cognitive_agent/infrastructure/can/__init__.py
|
||||
src/cognitive_agent/infrastructure/can/access.py
|
||||
src/cognitive_agent/infrastructure/can/gateway.py
|
||||
src/cognitive_agent/infrastructure/can/hardware.py
|
||||
src/cognitive_agent/infrastructure/can/matrix.py
|
||||
src/cognitive_agent/infrastructure/can/mqtt_transport.py
|
||||
src/cognitive_agent/infrastructure/can/zlgcan_bridge.py
|
||||
src/cognitive_agent/infrastructure/can/zlgcan_linux_bridge.py
|
||||
src/cognitive_agent/infrastructure/firmware/__init__.py
|
||||
@@ -50,6 +57,17 @@ src/cognitive_agent/infrastructure/llm/openai_responses.py
|
||||
src/cognitive_agent/infrastructure/nfc/__init__.py
|
||||
src/cognitive_agent/infrastructure/nfc/pn532.py
|
||||
src/cognitive_agent/infrastructure/nfc/reader.py
|
||||
src/cognitive_agent/infrastructure/speech/__init__.py
|
||||
src/cognitive_agent/infrastructure/speech/assembler.py
|
||||
src/cognitive_agent/infrastructure/speech/capability.py
|
||||
src/cognitive_agent/infrastructure/speech/contracts.py
|
||||
src/cognitive_agent/infrastructure/speech/recovery.py
|
||||
src/cognitive_agent/infrastructure/speech/semantic.py
|
||||
src/cognitive_agent/infrastructure/speech/sensevoice.py
|
||||
src/cognitive_agent/infrastructure/speech/tracing.py
|
||||
src/cognitive_agent/infrastructure/speech/understanding.py
|
||||
src/cognitive_agent/infrastructure/speech/vad.py
|
||||
src/cognitive_agent/infrastructure/speech/vocabulary.py
|
||||
src/cognitive_agent/runtime/contracts.py
|
||||
src/cognitive_agent/testing/__init__.py
|
||||
src/cognitive_agent/testing/ch1_hil.py
|
||||
src/cognitive_agent/testing/ch1_hil.py
|
||||
@@ -21,6 +21,10 @@ pyserial<4,>=3.5
|
||||
[llm]
|
||||
openai>=1.30
|
||||
|
||||
[speech]
|
||||
sherpa-onnx==1.13.6
|
||||
sherpa-onnx-bin==1.13.6
|
||||
|
||||
[test]
|
||||
pytest>=8
|
||||
pytest-cov>=5
|
||||
|
||||
@@ -36,9 +36,9 @@ from cognitive_agent.infrastructure.speech.vocabulary import (
|
||||
|
||||
_CLAUSE_SPLIT = re.compile(r"[\s,,;;。!?!?…]+")
|
||||
_POSITIVE_SPLIT = re.compile(
|
||||
rf"[\s,,;;]*({'|'.join(map(re.escape, POSITIVE_CONNECTORS))})[\s,,;;]*"
|
||||
rf"[\s,,;;]*({'|'.join(map(re.escape, sorted(POSITIVE_CONNECTORS, key=len, reverse=True)))})[\s,,;;]*"
|
||||
)
|
||||
_COORDINATION_SPLIT = re.compile(r"\s*(?:以及|和|与|及|、)\s*")
|
||||
_COORDINATION_SPLIT = re.compile(r"\s*(?:还有|以及|和|跟|与|及|、)\s*")
|
||||
_CORRECTION_PATTERN = re.compile(
|
||||
rf"[\s,,;;]*({'|'.join(map(re.escape, sorted(CORRECTION_MARKERS, key=len, reverse=True)))}|不[,,]+是)[\s,,;;]*"
|
||||
)
|
||||
@@ -62,6 +62,14 @@ _PERCENT = re.compile(r"(-?\d+(?:\.\d+)?)\s*[%%]")
|
||||
_CHINESE_PERCENT = re.compile(r"百分之([零〇一二两三四五六七八九十百点\d.]+)")
|
||||
_TERMINAL = re.compile(r"[。!?!?]+$")
|
||||
_CONTROL_SCAFFOLD = re.compile(r"^(?:把|将|请|给我|帮我)+")
|
||||
_STRONG_CONTINUATION_ACTION = re.compile(
|
||||
r"打开|开启|启动|关闭|关掉|关上|停止|调整|调节|设置|设成|设为|设到|"
|
||||
r"调到|调成|调为|变成|开到|向前|往前|朝前|向后|往后|朝后|"
|
||||
r"向上|往上|向下|往下|升高|降低"
|
||||
)
|
||||
_NON_NUMERIC_ONE = re.compile(
|
||||
r"(?:一下(?:子)?|一会儿?|一点(?:点|儿)?)(?![零〇一二两三四五六七八九])"
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -109,12 +117,7 @@ class UnderstandingTranscriptProcessor:
|
||||
blocks = [part for part in blocks if part not in POSITIVE_CONNECTORS]
|
||||
for raw_block in blocks:
|
||||
block = raw_block
|
||||
if (
|
||||
shared_target is not None
|
||||
and not device_terms_in(block)
|
||||
and not _CORRECTION_PATTERN.search(block)
|
||||
and not _CANCELLATION_PATTERN.search(block)
|
||||
):
|
||||
if shared_target is not None and self._should_inherit_shared_target(block):
|
||||
block = self._inherit_shared_target(block, shared_target)
|
||||
clauses.append(block)
|
||||
(
|
||||
@@ -269,7 +272,19 @@ class UnderstandingTranscriptProcessor:
|
||||
reasons.append("correction_not_resolved")
|
||||
return active, cancelled, reasons, bool(new.commands), resolution_ms
|
||||
|
||||
analyses = [self._analyse_fragment(item) for item in self._plain_fragments(block)]
|
||||
analyses: list[_FragmentAnalysis] = []
|
||||
local_shared_target: SpeechCommandIntent | None = None
|
||||
for item in self._plain_fragments(block):
|
||||
fragment = item
|
||||
if (
|
||||
local_shared_target is not None
|
||||
and self._should_inherit_shared_target(fragment)
|
||||
):
|
||||
fragment = self._inherit_shared_target(fragment, local_shared_target)
|
||||
analysis = self._analyse_fragment(fragment)
|
||||
analyses.append(analysis)
|
||||
if analysis.commands:
|
||||
local_shared_target = analysis.commands[-1]
|
||||
resolution_ms += sum(item.resolution_ms for item in analyses)
|
||||
for index, analysis in enumerate(analyses):
|
||||
following = analyses[index + 1] if index + 1 < len(analyses) else None
|
||||
@@ -329,6 +344,8 @@ class UnderstandingTranscriptProcessor:
|
||||
return application_analysis
|
||||
devices = device_terms_in(text)
|
||||
actions = action_terms_in(text)
|
||||
if devices and not actions and self._has_explicit_set_value(text):
|
||||
actions = [(0, 0, "set", "implicit_set")]
|
||||
mode = self.resolver.match_mode(text)
|
||||
if mode is not None and (devices or actions):
|
||||
term = (
|
||||
@@ -738,11 +755,18 @@ class UnderstandingTranscriptProcessor:
|
||||
def _commands_for(
|
||||
self, text: str, term: DeviceTerm, action: str, negated: bool
|
||||
) -> tuple[list[SpeechCommandIntent], list[str], float]:
|
||||
implicit_driver_temperature = (
|
||||
term.semantic_key == "hvac.power"
|
||||
and self._numeric_value(text) is not None
|
||||
and bool(re.search(r"摄氏度|度|温度", text))
|
||||
)
|
||||
default_scope = (
|
||||
"passenger"
|
||||
if term.domain == "seat"
|
||||
else "front_left"
|
||||
if term.domain == "window"
|
||||
else "driver"
|
||||
if term.semantic_key == "hvac.temperature" or implicit_driver_temperature
|
||||
else "all"
|
||||
if term.semantic_key == "reading_light.power"
|
||||
else "vehicle"
|
||||
@@ -1160,6 +1184,10 @@ class UnderstandingTranscriptProcessor:
|
||||
|
||||
def _inherit_shared_target(self, fragment: str, command: SpeechCommandIntent) -> str:
|
||||
target = self._display_target(command)
|
||||
if self._has_explicit_scope(fragment):
|
||||
spoken_scope = str(command.qualifiers.get("spoken_scope", ""))
|
||||
if spoken_scope and target.startswith(spoken_scope):
|
||||
target = target[len(spoken_scope) :]
|
||||
if (
|
||||
command.domain == "seat"
|
||||
and not command.qualifiers.get("target_explicit")
|
||||
@@ -1167,8 +1195,38 @@ class UnderstandingTranscriptProcessor:
|
||||
):
|
||||
scope = "主驾" if command.target_scope in {"driver", "front_left"} else "副驾"
|
||||
target = f"{scope}{target}"
|
||||
if not action_terms_in(fragment) and self._has_explicit_set_value(fragment):
|
||||
return f"{target}设置为{fragment}"
|
||||
return f"{target}{fragment}"
|
||||
|
||||
@staticmethod
|
||||
def _has_explicit_scope(text: str) -> bool:
|
||||
return any(
|
||||
alias in text
|
||||
for aliases in SCOPE_ALIASES.values()
|
||||
for alias in aliases
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _has_explicit_set_value(cls, text: str) -> bool:
|
||||
if cls._percentage_value(text) is not None or color_in(text) is not None:
|
||||
return True
|
||||
return cls._numeric_value(text) is not None and bool(
|
||||
re.search(r"摄氏度|百分比|度|档|级|[%%]", text)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _should_inherit_shared_target(cls, fragment: str) -> bool:
|
||||
if (
|
||||
device_terms_in(fragment)
|
||||
or _CORRECTION_PATTERN.search(fragment)
|
||||
or _CANCELLATION_PATTERN.search(fragment)
|
||||
):
|
||||
return False
|
||||
return bool(_STRONG_CONTINUATION_ACTION.search(fragment)) or cls._has_explicit_set_value(
|
||||
fragment
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _cancel_commands(
|
||||
commands: Iterable[SpeechCommandIntent], *, negated: bool = False
|
||||
@@ -1259,6 +1317,7 @@ class UnderstandingTranscriptProcessor:
|
||||
|
||||
@staticmethod
|
||||
def _numeric_value(text: str) -> int | float | None:
|
||||
text = _NON_NUMERIC_ONE.sub("", text)
|
||||
match = _NUMBER.search(text)
|
||||
if match:
|
||||
value = float(match.group(1))
|
||||
|
||||
@@ -44,7 +44,23 @@ DEVICE_TERMS: tuple[DeviceTerm, ...] = (
|
||||
"seat",
|
||||
"seat.longitudinal_position",
|
||||
"seat_longitudinal_motor",
|
||||
("座椅", "坐椅", "座位"),
|
||||
(
|
||||
"座椅",
|
||||
"坐椅",
|
||||
"座位",
|
||||
"驾驶座椅",
|
||||
"驾驶座",
|
||||
"驾驶席",
|
||||
"司机座椅",
|
||||
"司机座",
|
||||
"主驾驶座椅",
|
||||
"主驾驶座",
|
||||
"乘客座椅",
|
||||
"乘客座",
|
||||
"副驾驶座椅",
|
||||
"副驾驶座",
|
||||
"副驾驶席",
|
||||
),
|
||||
),
|
||||
DeviceTerm(
|
||||
"座椅坐垫",
|
||||
@@ -65,14 +81,42 @@ DEVICE_TERMS: tuple[DeviceTerm, ...] = (
|
||||
"seat",
|
||||
"seat.ventilation",
|
||||
"seat",
|
||||
("座椅通风", "通风座椅"),
|
||||
(
|
||||
"座椅通风",
|
||||
"通风座椅",
|
||||
"驾驶座通风",
|
||||
"驾驶席通风",
|
||||
"司机座通风",
|
||||
"司机座椅通风",
|
||||
"主驾驶座通风",
|
||||
"主驾驶座椅通风",
|
||||
"乘客座通风",
|
||||
"乘客座椅通风",
|
||||
"副驾驶座通风",
|
||||
"副驾驶座椅通风",
|
||||
"副驾驶席通风",
|
||||
),
|
||||
),
|
||||
DeviceTerm(
|
||||
"座椅加热",
|
||||
"seat",
|
||||
"seat.heating",
|
||||
"seat",
|
||||
("座椅加热", "加热座椅"),
|
||||
(
|
||||
"座椅加热",
|
||||
"加热座椅",
|
||||
"驾驶座加热",
|
||||
"驾驶席加热",
|
||||
"司机座加热",
|
||||
"司机座椅加热",
|
||||
"主驾驶座加热",
|
||||
"主驾驶座椅加热",
|
||||
"乘客座加热",
|
||||
"乘客座椅加热",
|
||||
"副驾驶座加热",
|
||||
"副驾驶座椅加热",
|
||||
"副驾驶席加热",
|
||||
),
|
||||
),
|
||||
DeviceTerm(
|
||||
"车窗",
|
||||
@@ -126,8 +170,26 @@ DEVICE_TERMS: tuple[DeviceTerm, ...] = (
|
||||
)
|
||||
|
||||
SCOPE_ALIASES: dict[str, tuple[str, ...]] = {
|
||||
"driver": ("主驾", "主驾驶", "驾驶位", "司机位", "左前"),
|
||||
"passenger": ("副驾", "副驾驶", "乘客位", "右前"),
|
||||
"driver": (
|
||||
"主驾",
|
||||
"主驾驶",
|
||||
"主驾驶座",
|
||||
"驾驶位",
|
||||
"驾驶座",
|
||||
"驾驶席",
|
||||
"司机位",
|
||||
"司机座",
|
||||
"左前",
|
||||
),
|
||||
"passenger": (
|
||||
"副驾",
|
||||
"副驾驶",
|
||||
"副驾驶座",
|
||||
"副驾驶席",
|
||||
"乘客位",
|
||||
"乘客座",
|
||||
"右前",
|
||||
),
|
||||
"front_row": ("前排",),
|
||||
"rear_row": ("后排",),
|
||||
"rear_left": ("左后", "后排左侧"),
|
||||
@@ -148,8 +210,12 @@ ACTION_ALIASES: dict[str, tuple[str, ...]] = {
|
||||
"设置为",
|
||||
"调到",
|
||||
"调成",
|
||||
"调为",
|
||||
"变成",
|
||||
"设为",
|
||||
"设成",
|
||||
"设到",
|
||||
"设置",
|
||||
"开到",
|
||||
),
|
||||
"forward": ("向前", "往前", "朝前", "前移", "前倾"),
|
||||
@@ -176,13 +242,23 @@ CORRECTION_MARKERS: tuple[str, ...] = (
|
||||
|
||||
CANCELLATION_MARKERS: tuple[str, ...] = ("算了", "取消", "作废")
|
||||
POSITIVE_CONNECTORS: tuple[str, ...] = (
|
||||
"接下来再",
|
||||
"紧接着",
|
||||
"再然后",
|
||||
"接下来",
|
||||
"然后",
|
||||
"同时",
|
||||
"并且",
|
||||
"接着",
|
||||
"随后",
|
||||
"之后",
|
||||
"而后",
|
||||
"顺便",
|
||||
"另外",
|
||||
"最后",
|
||||
"再",
|
||||
"并",
|
||||
"先",
|
||||
)
|
||||
NEGATION_PREFIXES: tuple[str, ...] = ("不要", "别", "不想", "无需", "不必", "禁止")
|
||||
UNITS: tuple[str, ...] = ("摄氏度", "百分比", "毫米", "厘米", "毫秒", "分钟", "度", "%", "档", "秒")
|
||||
|
||||
+13
-1
@@ -46,5 +46,17 @@
|
||||
{"name":"window_three_quarters","raw":"打开四分之三车窗","clean":"将车窗打开到75%。","capabilities":["window.set_position"],"cancelled":0,"uncertain":false,"execution_eligible":true},
|
||||
{"name":"shade_quarter_position","raw":"把遮阳帘打开到四分之一","clean":"将遮阳帘打开到25%。","capabilities":["roof.set_shade_position"],"cancelled":0,"uncertain":false,"execution_eligible":true},
|
||||
{"name":"shade_open_bridge_regression","raw":"把遮阳帘打开","clean":"打开遮阳帘。","capabilities":["roof.set_shade_motion"],"cancelled":0,"uncertain":false,"execution_eligible":true},
|
||||
{"name":"shade_half_open_bridge_regression","raw":"遮阳帘打开一半","clean":"将遮阳帘打开一半。","capabilities":["roof.set_shade_motion"],"cancelled":0,"uncertain":false,"execution_eligible":true}
|
||||
{"name":"shade_half_open_bridge_regression","raw":"遮阳帘打开一半","clean":"将遮阳帘打开一半。","capabilities":["roof.set_shade_motion"],"cancelled":0,"uncertain":false,"execution_eligible":true},
|
||||
{"name":"reported_driver_passenger_climate","raw":"打开主驾座椅通风,然后打开副驾座椅通风,然后将空调设置为24度。","clean":"打开主驾座椅通风,然后打开副驾座椅通风,然后将空调设置为24度。","capabilities":["seat.set_driver_ventilation_native","seat.set_ventilation","climate.set_temperature"],"cancelled":0,"uncertain":false,"execution_eligible":true},
|
||||
{"name":"sequenced_colloquial_controls","raw":"先打开主驾座椅通风,再打开副驾座椅通风,最后把空调调成二十四度","clean":"打开主驾座椅通风,然后打开副驾座椅通风,然后将空调设置为24度。","capabilities":["seat.set_driver_ventilation_native","seat.set_ventilation","climate.set_temperature"],"cancelled":0,"uncertain":false,"execution_eligible":true},
|
||||
{"name":"driver_seat_alias","raw":"把驾驶座通风打开","clean":"打开主驾座椅通风。","capabilities":["seat.set_driver_ventilation_native"],"cancelled":0,"uncertain":false,"execution_eligible":true},
|
||||
{"name":"passenger_seat_alias","raw":"把乘客座通风打开","clean":"打开副驾座椅通风。","capabilities":["seat.set_ventilation"],"cancelled":0,"uncertain":false,"execution_eligible":true},
|
||||
{"name":"pragmatic_yixia_not_level","raw":"给副驾驶开一下座椅通风","clean":"打开副驾座椅通风。","capabilities":["seat.set_ventilation"],"parameter_numbers":[],"cancelled":0,"uncertain":false,"execution_eligible":true},
|
||||
{"name":"alternative_sequence_connector","raw":"打开主驾座椅通风之后打开副驾座椅通风","clean":"打开主驾座椅通风,然后打开副驾座椅通风。","capabilities":["seat.set_driver_ventilation_native","seat.set_ventilation"],"cancelled":0,"uncertain":false,"execution_eligible":true},
|
||||
{"name":"additive_connector","raw":"打开主驾座椅通风还有副驾座椅通风","clean":"打开主驾座椅通风,然后打开副驾座椅通风。","capabilities":["seat.set_driver_ventilation_native","seat.set_ventilation"],"cancelled":0,"uncertain":false,"execution_eligible":true},
|
||||
{"name":"shared_property_scope_override","raw":"打开主驾通风,再给副驾也打开","clean":"打开主驾座椅通风,然后打开副驾座椅通风。","capabilities":["seat.set_driver_ventilation_native","seat.set_ventilation"],"cancelled":0,"uncertain":false,"execution_eligible":true},
|
||||
{"name":"elliptical_temperature","raw":"空调二十四度","clean":"将空调设置为24度。","capabilities":["climate.set_temperature"],"cancelled":0,"uncertain":false,"execution_eligible":true},
|
||||
{"name":"elliptical_value_continuation","raw":"打开空调,然后二十四度","clean":"打开空调,然后将空调设置为24度。","capabilities":["climate.set_power","climate.set_temperature"],"cancelled":0,"uncertain":false,"execution_eligible":true},
|
||||
{"name":"punctuated_ambient_slots","raw":"氛围灯打开,调成蓝色,亮度百分之五十","clean":"打开氛围灯,然后将氛围灯颜色调整为蓝色并将亮度设置为50%。","capabilities":["ambient_light.set_power","ambient_light.set_illumination"],"cancelled":0,"uncertain":false,"execution_eligible":true},
|
||||
{"name":"non_control_continuation_fails_closed","raw":"打开空调,然后开玩笑。","clean":"打开空调。","capabilities":["climate.set_power"],"cancelled":0,"uncertain":true,"execution_eligible":false}
|
||||
]
|
||||
|
||||
@@ -430,6 +430,99 @@ class SpeechCapabilityAlignmentTests(unittest.TestCase):
|
||||
self.assertEqual(graded.commands[-1].parameters["level"], 1)
|
||||
self.assertIn("高档", graded.clean_text)
|
||||
|
||||
def test_colloquial_seat_aliases_do_not_turn_pragmatic_one_into_level(self) -> None:
|
||||
cases = (
|
||||
("把驾驶座通风打开", "driver", "seat.set_driver_ventilation_native"),
|
||||
("把乘客座通风打开", "passenger", "seat.set_ventilation"),
|
||||
("给副驾驶开一下座椅通风", "passenger", "seat.set_ventilation"),
|
||||
)
|
||||
for text, target, capability in cases:
|
||||
with self.subTest(text=text):
|
||||
result = self.processor.process(text)
|
||||
self.assertTrue(result.execution_eligible)
|
||||
self.assertEqual(len(result.commands), 1)
|
||||
self.assertEqual(result.commands[0].target_scope, target)
|
||||
self.assertEqual(result.commands[0].capability, capability)
|
||||
self.assertEqual(result.commands[0].parameters, {"state": "on"})
|
||||
|
||||
explicit_level = self.processor.process("主驾座椅通风开到一档")
|
||||
self.assertEqual(explicit_level.commands[0].parameters["level"], 1)
|
||||
|
||||
def test_discourse_connectors_preserve_actions_without_leaking_scope(self) -> None:
|
||||
cases = (
|
||||
"打开主驾座椅通风,然后打开副驾座椅通风,然后将空调设置为24度。",
|
||||
"先打开主驾座椅通风,再打开副驾座椅通风,最后把空调调成二十四度",
|
||||
)
|
||||
expected = (
|
||||
("seat.set_driver_ventilation_native", "driver", {"state": "on", "level": 2}),
|
||||
("seat.set_ventilation", "passenger", {"state": "on"}),
|
||||
("climate.set_temperature", "driver", {"temperature_c": 24}),
|
||||
)
|
||||
for text in cases:
|
||||
with self.subTest(text=text):
|
||||
result = self.processor.process(text)
|
||||
self.assertEqual(
|
||||
tuple(
|
||||
(command.capability, command.target_scope, command.parameters)
|
||||
for command in result.commands
|
||||
),
|
||||
expected,
|
||||
)
|
||||
self.assertTrue(result.execution_eligible)
|
||||
|
||||
for text in (
|
||||
"打开主驾座椅通风还有副驾座椅通风",
|
||||
"打开主驾座椅通风之后打开副驾座椅通风",
|
||||
"打开主驾通风,再给副驾也打开",
|
||||
):
|
||||
with self.subTest(text=text):
|
||||
result = self.processor.process(text)
|
||||
self.assertEqual(
|
||||
[(command.capability, command.target_scope) for command in result.commands],
|
||||
[
|
||||
("seat.set_driver_ventilation_native", "driver"),
|
||||
("seat.set_ventilation", "passenger"),
|
||||
],
|
||||
)
|
||||
self.assertTrue(result.execution_eligible)
|
||||
|
||||
def test_elliptical_values_inherit_only_an_authoritative_property(self) -> None:
|
||||
temperature = self.processor.process("空调二十四度")
|
||||
self.assertEqual(len(temperature.commands), 1)
|
||||
self.assertEqual(temperature.commands[0].capability, "climate.set_temperature")
|
||||
self.assertEqual(temperature.commands[0].target_scope, "driver")
|
||||
self.assertEqual(temperature.commands[0].parameters, {"temperature_c": 24})
|
||||
self.assertTrue(temperature.execution_eligible)
|
||||
|
||||
continued = self.processor.process("打开空调,然后二十四度")
|
||||
self.assertEqual(
|
||||
[(command.capability, command.target_scope) for command in continued.commands],
|
||||
[("climate.set_power", "vehicle"), ("climate.set_temperature", "driver")],
|
||||
)
|
||||
self.assertTrue(continued.execution_eligible)
|
||||
|
||||
ambient = self.processor.process("氛围灯打开,调成蓝色,亮度百分之五十")
|
||||
self.assertEqual(
|
||||
[command.capability for command in ambient.commands],
|
||||
["ambient_light.set_power", "ambient_light.set_illumination"],
|
||||
)
|
||||
self.assertEqual(
|
||||
ambient.commands[-1].parameters,
|
||||
{"color_hex": "#0000FF", "brightness_pct": 50},
|
||||
)
|
||||
self.assertTrue(ambient.execution_eligible)
|
||||
|
||||
def test_non_control_continuation_never_replays_previous_action(self) -> None:
|
||||
for text in ("打开空调,然后开玩笑", "打开空调,然后开会"):
|
||||
with self.subTest(text=text):
|
||||
result = self.processor.process(text)
|
||||
self.assertEqual(
|
||||
[command.capability for command in result.commands],
|
||||
["climate.set_power"],
|
||||
)
|
||||
self.assertTrue(result.uncertain)
|
||||
self.assertFalse(result.execution_eligible)
|
||||
|
||||
def test_unsupported_semantics_never_create_fake_executor_payloads(self) -> None:
|
||||
for text in ("打开前大灯", "把主驾座椅调整为零重力模式"):
|
||||
with self.subTest(text=text):
|
||||
|
||||
Reference in New Issue
Block a user