feat: integrate cabin CAN and verified dynamic seat modes
This commit is contained in:
+133
-32
@@ -2641,7 +2641,7 @@ function updateSeatFeedbackModeLabel() {
|
||||
}
|
||||
|
||||
function selectExecutionMode(mode) {
|
||||
if (mode === "hardware" && !state.runtime?.hardware?.configured) return;
|
||||
if (mode === "hardware" && !(state.runtime?.hardware?.configured && state.runtime?.vehicle_can?.configured)) return;
|
||||
if (mode === "simulated_hardware" && !state.runtime?.simulated_hardware?.enabled) return;
|
||||
state.executionMode = mode;
|
||||
$$('[data-execution-mode]').forEach((button) => {
|
||||
@@ -2663,7 +2663,7 @@ function selectExecutionMode(mode) {
|
||||
? "真实台架安全门禁"
|
||||
: simulatedHardware ? "模拟台架隔离边界" : "虚拟执行边界";
|
||||
$("#safetyText").textContent = hardware
|
||||
? "副驾座椅映射到统一实体执行器;绝对位置会实时检查板端标定,小幅相对移动可走受限定时/Hall 回读,异常会尝试 STOP_ALL。"
|
||||
? "同一 CAN 盒由单进程持有:通道 0 控制副驾座椅并校验板端反馈,通道 1 以 ISO CAN FD 500 kbit/s / 2 Mbit/s 控制空调等车身设备并校验状态帧。"
|
||||
: simulatedHardware
|
||||
? "故障只注入执行报告;物理 CAN、ZLG 适配器和 IAR 固件路径保持关闭。"
|
||||
: "四域 CAN 均为 dry-run,真实发送被强制关闭。";
|
||||
@@ -2807,6 +2807,11 @@ function modeChipLabel(mode) {
|
||||
: name;
|
||||
}
|
||||
|
||||
function modeSecondsLabel(durationMs) {
|
||||
const seconds = Number(durationMs) / 1000;
|
||||
return Number.isInteger(seconds) ? String(seconds) : seconds.toFixed(1);
|
||||
}
|
||||
|
||||
function populateModeChips() {
|
||||
const group = $(".mode-chips");
|
||||
if (!group) return;
|
||||
@@ -2825,7 +2830,41 @@ function populateModeChips() {
|
||||
}
|
||||
button.dataset.modeProfileId = mode.id;
|
||||
button.dataset.modeCommand = `进入${mode.name}`;
|
||||
button.title = mode.description || mode.name;
|
||||
const timeline = mode.timeline;
|
||||
let cycleBadge = $(".mode-cycle-badge", button);
|
||||
if (["infinite", "until_stopped_or_timeout"].includes(timeline?.repeat)) {
|
||||
if (!cycleBadge) {
|
||||
cycleBadge = document.createElement("small");
|
||||
cycleBadge.className = "mode-cycle-badge";
|
||||
button.append(cycleBadge);
|
||||
}
|
||||
const runtimeMinutes = Number(timeline.max_runtime_ms || 0) / 60000;
|
||||
const runtimeLabel = Number.isInteger(runtimeMinutes)
|
||||
? String(runtimeMinutes)
|
||||
: runtimeMinutes.toFixed(1);
|
||||
const cycleDurationMs = Number(timeline.cycle_duration_ms || 0);
|
||||
const cycleLabel = cycleDurationMs > 0
|
||||
? `${modeSecondsLabel(cycleDurationMs)}s循环`
|
||||
: "目标到位循环";
|
||||
cycleBadge.textContent = runtimeMinutes > 0
|
||||
? `${cycleLabel} · 最长${runtimeLabel}min`
|
||||
: cycleLabel;
|
||||
button.dataset.modeBehavior = "cyclic";
|
||||
} else if (cycleBadge) {
|
||||
cycleBadge.remove();
|
||||
}
|
||||
const phaseSummary = Array.isArray(timeline?.phases)
|
||||
? timeline.phases.map((phase) => {
|
||||
if (Number(phase.duration_ms || 0) > 0) {
|
||||
return `${phase.name} ${modeSecondsLabel(phase.duration_ms)}s`;
|
||||
}
|
||||
const timeout = Number(phase.timeout_ms || 0);
|
||||
return timeout > 0
|
||||
? `${phase.name} 目标到位(保护上限${modeSecondsLabel(timeout)}s)`
|
||||
: `${phase.name} 目标到位`;
|
||||
}).join(" → ")
|
||||
: "";
|
||||
button.title = [mode.description || mode.name, phaseSummary].filter(Boolean).join("\n");
|
||||
});
|
||||
refreshIcons();
|
||||
}
|
||||
@@ -2864,6 +2903,8 @@ async function loadRuntime() {
|
||||
populateVehicleSelector();
|
||||
populateModeChips();
|
||||
const hardware = state.runtime.hardware || {};
|
||||
const vehicleCan = state.runtime.vehicle_can || {};
|
||||
const linkedHardwareReady = Boolean(hardware.configured && vehicleCan.configured);
|
||||
const badge = $("#modelBadge");
|
||||
const gptButton = $('[data-provider="openai"]');
|
||||
if (state.runtime.openai_available) {
|
||||
@@ -2882,7 +2923,7 @@ async function loadRuntime() {
|
||||
}
|
||||
$("#matrixCount").textContent = `${state.runtime.matrix.messages} / ${state.runtime.matrix.signals}`;
|
||||
const hardwareButton = $("#hardwareMode");
|
||||
hardwareButton.disabled = !hardware.configured;
|
||||
hardwareButton.disabled = !linkedHardwareReady;
|
||||
const simulation = state.runtime.simulated_hardware || {};
|
||||
$("#simulationMode").disabled = !simulation.enabled;
|
||||
$("#simulationMode").title = simulation.enabled
|
||||
@@ -2897,23 +2938,23 @@ async function loadRuntime() {
|
||||
const accessText = hardware.access_policy === "local_approval_idle_lease"
|
||||
? ` · 本地审批 · 空闲 ${hardware.access_inactivity_timeout_seconds || 180} 秒自动停止`
|
||||
: "";
|
||||
$("#hardwareNote").textContent = hardware.configured
|
||||
? `${hardware.backend} · ${hardware.bitrate / 1000} kbit/s · ${profileText} · ${flashText} · ${approvalText}${accessText}${concurrencyText}`
|
||||
: `真实执行已锁定:${profileText};请完成服务端启用、适配器、固件镜像${hardware.approval_required ? "和令牌" : ""}配置`;
|
||||
$("#hardwareAliasNote").textContent = "副驾座椅 → t2g_primary_seat(统一电机、加热和通风执行器)";
|
||||
$("#hardwareNote").textContent = linkedHardwareReady
|
||||
? `${hardware.backend} · CH${hardware.channel} 座椅 ${hardware.bitrate / 1000} kbit/s · CH${vehicleCan.channel} 车身 CAN FD ${vehicleCan.arbitration.bitrate / 1000}k/${vehicleCan.data.bitrate / 1000000}M · ${profileText} · ${flashText} · ${approvalText}${accessText}${concurrencyText}`
|
||||
: `联动真实执行已锁定:${profileText};请完成座椅固件、双通道 ZLG 适配器、车身 CAN FD${hardware.approval_required ? "和令牌" : ""}配置`;
|
||||
$("#hardwareAliasNote").textContent = "CH0 → 副驾座椅控制器 · CH1 → 空调/车窗/门锁/灯光/遮阳帘(ISO CAN FD)";
|
||||
$("#autoFlashNote").textContent = hardware.auto_flash_enabled
|
||||
? "未检测到匹配固件时,将在发送座椅控制帧前通过 IAR C-SPY 自动烧录并重新验证。"
|
||||
: "仅接受周期反馈与四轴量程签名匹配的统一固件;需要自动烧录时请启用服务端开关。";
|
||||
$("#hardwareBadge").className = `status-badge ${hardware.configured ? "" : "danger"}`.trim();
|
||||
$("#hardwareBadge").innerHTML = hardware.configured
|
||||
? '<i data-lucide="shield-check"></i>硬件已配置'
|
||||
$("#hardwareBadge").className = `status-badge ${linkedHardwareReady ? "" : "danger"}`.trim();
|
||||
$("#hardwareBadge").innerHTML = linkedHardwareReady
|
||||
? '<i data-lucide="shield-check"></i>双通道已配置'
|
||||
: '<i data-lucide="shield-off"></i>硬件锁定';
|
||||
$("#hardwareCoverage").textContent = hardware.configured
|
||||
? `可选 · ${approvalText}${accessText}${concurrencyText}`
|
||||
$("#hardwareCoverage").textContent = linkedHardwareReady
|
||||
? `CH0 座椅 + CH${vehicleCan.channel} 车身 · ${approvalText}${accessText}${concurrencyText}`
|
||||
: "已阻断";
|
||||
$("#hardwareCoverage").className = hardware.configured ? "available" : "blocked";
|
||||
$("#hardwareCoverage").className = linkedHardwareReady ? "available" : "blocked";
|
||||
const modeAvailable = state.executionMode === "hardware"
|
||||
? hardware.configured
|
||||
? linkedHardwareReady
|
||||
: state.executionMode === "simulated_hardware"
|
||||
? simulation.enabled
|
||||
: true;
|
||||
@@ -3136,6 +3177,20 @@ function mergeSeatActuatorState(previousState, patchState) {
|
||||
mergedSeat.position = mergedPosition;
|
||||
return;
|
||||
}
|
||||
if (feature === "dynamic_mode") {
|
||||
const item = $(`.seat-card[data-seat="${seat}"] [data-dynamic-mode]`);
|
||||
if (!item) return;
|
||||
const running = value.state === "running";
|
||||
item.classList.toggle("running", running);
|
||||
const runtimeMinutes = Number(value.max_runtime_ms || 600000) / 60000;
|
||||
const runtimeLabel = Number.isInteger(runtimeMinutes)
|
||||
? String(runtimeMinutes)
|
||||
: runtimeMinutes.toFixed(1);
|
||||
$("b", item).textContent = running
|
||||
? `${value.name} · 循环中 · 最长${runtimeLabel}分钟`
|
||||
: "未运行";
|
||||
return;
|
||||
}
|
||||
mergedSeat[feature] = value && typeof value === "object" && !Array.isArray(value)
|
||||
? { ...(previousSeat[feature] || {}), ...value }
|
||||
: value;
|
||||
@@ -3317,6 +3372,10 @@ const CONFIG_DOMAIN_META = {
|
||||
climate: { label: "空调", icon: "fan", className: "climate" },
|
||||
ambient_light: { label: "氛围灯", icon: "lamp-ceiling", className: "ambient" },
|
||||
roof: { label: "智能天幕", icon: "panels-top-left", className: "roof" },
|
||||
door: { label: "车门锁", icon: "door-open", className: "generic" },
|
||||
window: { label: "车窗", icon: "panels-top-left", className: "generic" },
|
||||
outlet: { label: "电动风口", icon: "wind", className: "climate" },
|
||||
interior_light: { label: "车内灯", icon: "lightbulb", className: "ambient" },
|
||||
};
|
||||
|
||||
const CONFIG_CAPABILITY_LABELS = {
|
||||
@@ -3328,6 +3387,8 @@ const CONFIG_CAPABILITY_LABELS = {
|
||||
"seat.set_backrest_recline": "调节靠背角度",
|
||||
"seat.set_legrest_height": "调节腿托角度",
|
||||
"seat.set_legrest_extension": "调节腿托伸缩",
|
||||
"seat.start_dynamic_mode": "启动动态座椅模式",
|
||||
"seat.stop_dynamic_mode": "停止动态座椅模式",
|
||||
"climate.set_temperature": "设置空调温度",
|
||||
"climate.set_fan_level": "设置空调风量",
|
||||
"climate.set_power": "设置空调电源",
|
||||
@@ -3335,6 +3396,15 @@ const CONFIG_CAPABILITY_LABELS = {
|
||||
"climate.set_cycle_mode": "设置内外循环",
|
||||
"climate.set_outlet_mode": "设置出风模式",
|
||||
"climate.set_front_defrost": "设置前风挡除霜",
|
||||
"climate.set_control_mode": "设置空调控制模式",
|
||||
"door.set_rear_lock": "设置后排门锁",
|
||||
"window.set_motion": "控制车窗动作",
|
||||
"window.set_position": "设置车窗开度",
|
||||
"outlet.set_position_percent": "设置电动风口位置",
|
||||
"outlet.set_calibrated_position": "设置电动风口标定位置",
|
||||
"interior_light.set_indoor_power": "设置室内灯",
|
||||
"interior_light.set_wall_power": "设置壁灯",
|
||||
"interior_light.set_reading_power": "设置阅读灯",
|
||||
"ambient_light.set_power": "设置氛围灯电源",
|
||||
"ambient_light.set_illumination": "设置氛围灯颜色与亮度",
|
||||
"ambient_light.set_color": "设置氛围灯颜色",
|
||||
@@ -3342,6 +3412,7 @@ const CONFIG_CAPABILITY_LABELS = {
|
||||
"ambient_light.set_mode": "设置氛围灯模式",
|
||||
"roof.set_transmittance": "设置天幕透光率",
|
||||
"roof.set_shade_position": "设置遮阳帘位置",
|
||||
"roof.set_shade_motion": "控制遮阳帘动作",
|
||||
"roof.set_automatic_dimming": "设置自动调光",
|
||||
};
|
||||
|
||||
@@ -3361,6 +3432,7 @@ const CONFIG_PARAMETER_LABELS = {
|
||||
cycle_mode: "循环模式",
|
||||
outlet_mode: "出风模式",
|
||||
mode: "工作模式",
|
||||
mode_id: "固件模式编号",
|
||||
};
|
||||
|
||||
function makeConfigElement(tagName, className, textValue) {
|
||||
@@ -3427,8 +3499,13 @@ function renderConfigCards(result) {
|
||||
const container = $("#configCards");
|
||||
container.replaceChildren();
|
||||
const entries = configEntries(result);
|
||||
const operations = entries.flatMap(([domain, config]) => (
|
||||
(config?.spec?.operations || []).map((operation) => ({ domain, config, operation }))
|
||||
const operations = entries.flatMap(([configKey, config]) => (
|
||||
(config?.spec?.operations || []).map((operation) => ({
|
||||
configKey,
|
||||
domain: config?.spec?.domain || configKey,
|
||||
config,
|
||||
operation,
|
||||
}))
|
||||
));
|
||||
if (!operations.length) {
|
||||
const empty = makeConfigElement("div", "config-empty");
|
||||
@@ -3457,7 +3534,7 @@ function renderConfigCards(result) {
|
||||
sceneSummary.append(sceneCopy, sceneStats);
|
||||
container.append(sceneSummary);
|
||||
|
||||
operations.forEach(({ domain, config, operation }, index) => {
|
||||
operations.forEach(({ configKey, domain, config, operation }, index) => {
|
||||
const meta = CONFIG_DOMAIN_META[domain] || { label: domain, icon: "box", className: "generic" };
|
||||
const card = makeConfigElement("article", `config-card config-${meta.className}`);
|
||||
const header = makeConfigElement("header", "config-card-header");
|
||||
@@ -3499,7 +3576,7 @@ function renderConfigCards(result) {
|
||||
});
|
||||
parametersBlock.append(parameterGrid);
|
||||
|
||||
const plan = planForDomain(result, domain);
|
||||
const plan = planForDomain(result, configKey);
|
||||
const allCommands = plan?.spec?.commands || [];
|
||||
const matchedCommands = allCommands.filter((command) => !command.operation_ref || command.operation_ref === operation.id);
|
||||
const command = matchedCommands[0] || null;
|
||||
@@ -3528,10 +3605,16 @@ function renderConfigCards(result) {
|
||||
hardwareBlock.append(signalList);
|
||||
|
||||
const footer = makeConfigElement("footer", "config-card-footer");
|
||||
const executionScope = config.metadata?.labels?.execution_scope;
|
||||
const executionScopeLabel = result.execution_mode === "hardware" && domain === "seat"
|
||||
? "座椅 CAN · CH0"
|
||||
: executionScope === "vehicle_can_channel_1"
|
||||
? "车身 CAN · CH1"
|
||||
: executionScope === "virtual_only" ? "仅虚拟执行" : "已通过配置校验";
|
||||
footer.append(
|
||||
makeConfigElement("span", "", `超时 ${operation.timeout_ms ?? "--"} ms`),
|
||||
makeConfigElement("span", "", operation.failure_policy === "continue" ? "失败后继续" : "失败即中止"),
|
||||
makeConfigElement("span", "", config.metadata?.labels?.execution_scope === "virtual_only" ? "仅虚拟执行" : "已通过配置校验"),
|
||||
makeConfigElement("span", "", executionScopeLabel),
|
||||
);
|
||||
|
||||
card.append(header, objectBlock, parametersBlock, hardwareBlock, footer);
|
||||
@@ -3605,7 +3688,8 @@ function renderExecution(result) {
|
||||
return;
|
||||
}
|
||||
const reportSucceeded = report.spec.status === "succeeded";
|
||||
summary.className = `execution-summary ${reportSucceeded ? "succeeded" : "failed"}`;
|
||||
const partiallySucceeded = result.status === "partially_succeeded";
|
||||
summary.className = `execution-summary ${reportSucceeded ? "succeeded" : partiallySucceeded ? "partial" : "failed"}`;
|
||||
const presetSave = Boolean(result.preset_config);
|
||||
const modeSave = Boolean(result.mode_profile);
|
||||
const simulatedHil = report.metadata?.labels?.seat_feedback === "simulated_hardware";
|
||||
@@ -3614,6 +3698,10 @@ function renderExecution(result) {
|
||||
const motionCount = report.spec.results.filter((item) => item.telemetry?.motion_axis).length;
|
||||
const firmwareResult = report.spec.results.find((item) => item.telemetry?.firmware_verified);
|
||||
const operationResults = report.spec.results.filter((item) => item.ref !== "seat-controller-firmware");
|
||||
const succeededOperations = operationResults.filter((item) => item.status === "succeeded");
|
||||
const failedOperations = operationResults.filter((item) => item.status === "failed");
|
||||
const succeededDomains = [...new Set(succeededOperations.map((item) => item.domain))];
|
||||
const failedDomains = [...new Set(failedOperations.map((item) => item.domain))];
|
||||
let executionDetail = modeSave
|
||||
? `长期模式 ${result.mode_profile.name} 已写入配置库,未生成 CAN 命令`
|
||||
: presetSave
|
||||
@@ -3623,13 +3711,16 @@ function renderExecution(result) {
|
||||
: physical
|
||||
? `${executedDomains.join(" / ")} 已下发真实 CAN,并以 IAR 固件实际反馈确认 ${operationResults.filter((item) => item.status === "succeeded").length} 个步骤`
|
||||
: `${executedDomains.join(" / ")} 共 ${report.spec.results.length} 个操作已完成 CAN 编码与虚拟执行`;
|
||||
if (partiallySucceeded) {
|
||||
executionDetail = `部分执行完成:${succeededDomains.join(" / ")} 成功 ${succeededOperations.length} 项;${failedDomains.join(" / ")} 未完成 ${failedOperations.length} 项,失败设备未阻断其他设备`;
|
||||
}
|
||||
if (motionCount) executionDetail += `,含 ${motionCount} 个座椅位置电机反馈`;
|
||||
if (firmwareResult?.telemetry?.programmed_this_request) executionDetail += ";本次已自动烧录并校验统一固件";
|
||||
else if (firmwareResult) executionDetail += ";板上统一固件协议签名已校验";
|
||||
if (result.status === "partially_succeeded") {
|
||||
if (result.deferred_requests?.length) {
|
||||
executionDetail += ";未接入域请求已保留为 deferred request";
|
||||
}
|
||||
if (!reportSucceeded) {
|
||||
if (!reportSucceeded && !partiallySucceeded) {
|
||||
const failure = report.spec.results.find((item) => item.status === "failed");
|
||||
const failureTelemetry = failure?.telemetry || {};
|
||||
executionDetail = simulatedHil
|
||||
@@ -3655,13 +3746,18 @@ function renderExecution(result) {
|
||||
: presetSave ? "姿态预设已保存"
|
||||
: simulatedHil ? "模拟台架执行成功"
|
||||
: physical ? "真实台架执行成功" : "虚拟执行成功"
|
||||
: simulatedHil ? "模拟故障已触发" : physical ? "真实台架执行失败" : "虚拟执行失败";
|
||||
summary.innerHTML = `<i data-lucide="${reportSucceeded ? ((presetSave || modeSave) ? "bookmark-check" : "circle-check-big") : "circle-x"}"></i><div><strong>${executionTitle}</strong><span>${executionDetail}</span></div>`;
|
||||
: partiallySucceeded ? (physical ? "真实台架部分执行成功" : "部分执行成功")
|
||||
: simulatedHil ? "模拟故障已触发" : physical ? "真实台架执行失败" : "虚拟执行失败";
|
||||
const executionIcon = reportSucceeded
|
||||
? ((presetSave || modeSave) ? "bookmark-check" : "circle-check-big")
|
||||
: partiallySucceeded ? "circle-alert" : "circle-x";
|
||||
summary.innerHTML = `<i data-lucide="${executionIcon}"></i><div><strong>${executionTitle}</strong><span>${executionDetail}</span></div>`;
|
||||
$("#resultNavMeta").textContent = reportSucceeded
|
||||
? `${report.spec.results.length} 项完成 · ${simulatedHil ? "SIM-HIL" : physical ? "HIL" : "DRY-RUN"}`
|
||||
: "执行失败 · 查看回读";
|
||||
$("#resultPanelState").textContent = reportSucceeded ? "SUCCEEDED" : "FAILED";
|
||||
$("#resultPanelState").className = `workspace-panel-state ${reportSucceeded ? "succeeded" : "failed"}`;
|
||||
: partiallySucceeded ? `${succeededOperations.length} 项成功 / ${failedOperations.length} 项失败 · 查看明细`
|
||||
: "执行失败 · 查看回读";
|
||||
$("#resultPanelState").textContent = reportSucceeded ? "SUCCEEDED" : partiallySucceeded ? "PARTIAL" : "FAILED";
|
||||
$("#resultPanelState").className = `workspace-panel-state ${reportSucceeded ? "succeeded" : partiallySucceeded ? "partial" : "failed"}`;
|
||||
report.spec.results.forEach((item) => {
|
||||
const row = document.createElement("div");
|
||||
row.className = "result-item";
|
||||
@@ -3706,7 +3802,8 @@ function renderResult(result) {
|
||||
} else if (result.function_configs?.seat) {
|
||||
state.seatFeedbackSource = "simulated";
|
||||
}
|
||||
const executionSucceeded = ["succeeded", "partially_succeeded"].includes(result.status);
|
||||
const partiallySucceeded = result.status === "partially_succeeded";
|
||||
const executionSucceeded = result.status === "succeeded" || partiallySucceeded;
|
||||
const domains = Object.keys(result.function_configs || {});
|
||||
const domainLabels = {
|
||||
seat: "座椅",
|
||||
@@ -3714,7 +3811,9 @@ function renderResult(result) {
|
||||
roof: "天幕",
|
||||
ambient_light: "氛围灯",
|
||||
};
|
||||
$("#resultTitle").textContent = executionSucceeded
|
||||
$("#resultTitle").textContent = partiallySucceeded
|
||||
? `${domains.length} 域场景部分完成`
|
||||
: executionSucceeded
|
||||
? result.mode_profile
|
||||
? "长期模式配置已保存"
|
||||
: result.preset_config
|
||||
@@ -3727,7 +3826,7 @@ function renderResult(result) {
|
||||
$("#requestMeta").textContent = `${result.request_id} / context:${conversationLabel} / turn:${result.conversation_turn} / ${result.provider_used || result.provider_requested}`;
|
||||
$("#requestMeta").textContent += ` / ${result.execution_report?.spec?.mode || result.execution_mode || "dry_run"}`;
|
||||
setRunStatus(
|
||||
executionSucceeded ? "succeeded" : "failed",
|
||||
partiallySucceeded ? "partial" : executionSucceeded ? "succeeded" : "failed",
|
||||
result.status.toUpperCase(),
|
||||
);
|
||||
renderPipeline(result.trace_events || []);
|
||||
@@ -3818,7 +3917,9 @@ async function submitRequest(event) {
|
||||
const completed = ["succeeded", "partially_succeeded"].includes(payload.status);
|
||||
const simulated = payload.execution_report?.metadata?.labels?.seat_feedback === "simulated_hardware";
|
||||
showToast(completed
|
||||
? (simulated
|
||||
? (payload.status === "partially_succeeded"
|
||||
? "已执行可用设备;部分设备未完成,详见执行结果"
|
||||
: simulated
|
||||
? "模拟台架执行完成"
|
||||
: payload.execution_report?.spec?.mode === "hil"
|
||||
? "真实台架执行与反馈确认已完成"
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="icon" href="/icons/app-icon-192.png" />
|
||||
<title>智舱随行 · 一句话与手势控制</title>
|
||||
<link rel="stylesheet" href="/styles.css?v=android-pad-2" />
|
||||
<link rel="stylesheet" href="/styles.css?v=android-pad-3" />
|
||||
<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-1" defer></script>
|
||||
<script src="/app.js?v=seat-hil-9" defer></script>
|
||||
<script src="/app.js?v=seat-hil-13" defer></script>
|
||||
<script type="module" src="/gesture-control.js?v=android-pad-1"></script>
|
||||
<script src="/pwa.js?v=android-pad-1" defer></script>
|
||||
</head>
|
||||
@@ -155,6 +155,9 @@
|
||||
|
||||
<div class="example-group">
|
||||
<span class="field-label">示例请求</span>
|
||||
<button type="button" class="example-command featured-example" data-example="副驾座椅通风3档,同时打开空调,温度设为24度,风量3档">
|
||||
<i data-lucide="workflow"></i><span>副驾通风 + 空调 24℃ · 双通道联动</span><i data-lucide="chevron-right"></i>
|
||||
</button>
|
||||
<button type="button" class="example-command" data-example="空调打开,车内温度调到24度,风量3档">
|
||||
<i data-lucide="fan"></i><span>空调 24℃ · 风量 3 档</span><i data-lucide="chevron-right"></i>
|
||||
</button>
|
||||
@@ -378,6 +381,9 @@
|
||||
<div class="seat-grid">
|
||||
<article class="seat-card" data-seat="passenger">
|
||||
<div class="seat-identity"><i data-lucide="armchair"></i><div><strong>副驾</strong><span>front_right</span></div></div>
|
||||
<div class="dynamic-mode-state" data-dynamic-mode>
|
||||
<i data-lucide="waves"></i><span>动态模式</span><b>未运行</b>
|
||||
</div>
|
||||
<div class="thermal-states">
|
||||
<span data-function="ventilation"><i data-lucide="wind"></i>通风 <b>--</b></span>
|
||||
<span data-function="heating"><i data-lucide="flame"></i>加热 <b>--</b></span>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
const CACHE_VERSION = "cognitive-agent-pad-v9";
|
||||
const CACHE_VERSION = "cognitive-agent-pad-v13";
|
||||
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
|
||||
const RUNTIME_CACHE = `${CACHE_VERSION}-runtime`;
|
||||
const APP_SHELL = [
|
||||
"/",
|
||||
"/index.html",
|
||||
"/styles.css?v=android-pad-2",
|
||||
"/styles.css?v=android-pad-3",
|
||||
"/network.js?v=android-pad-1",
|
||||
"/app.js?v=seat-hil-9",
|
||||
"/app.js?v=seat-hil-13",
|
||||
"/gesture-control.js?v=android-pad-1",
|
||||
"/pwa.js?v=android-pad-1",
|
||||
"/manifest.webmanifest",
|
||||
|
||||
@@ -1325,6 +1325,11 @@ textarea:focus {
|
||||
background: var(--teal-soft);
|
||||
}
|
||||
|
||||
.run-status.partial {
|
||||
color: var(--amber);
|
||||
background: var(--amber-soft);
|
||||
}
|
||||
|
||||
.run-status.failed {
|
||||
color: var(--red);
|
||||
background: var(--red-soft);
|
||||
@@ -2733,6 +2738,11 @@ pre {
|
||||
border-left-color: var(--teal);
|
||||
}
|
||||
|
||||
.execution-summary.partial {
|
||||
background: var(--amber-soft);
|
||||
border-left-color: var(--amber);
|
||||
}
|
||||
|
||||
.execution-summary.failed {
|
||||
background: var(--red-soft);
|
||||
border-left-color: var(--red);
|
||||
@@ -3892,6 +3902,11 @@ pre {
|
||||
background: var(--teal-soft);
|
||||
}
|
||||
|
||||
.workspace-panel-state.partial {
|
||||
color: var(--amber);
|
||||
background: var(--amber-soft);
|
||||
}
|
||||
|
||||
.workspace-panel-state.failed {
|
||||
color: var(--red);
|
||||
background: var(--red-soft);
|
||||
@@ -4357,6 +4372,44 @@ pre {
|
||||
}
|
||||
}
|
||||
|
||||
.mode-cycle-badge {
|
||||
margin-left: 2px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 999px;
|
||||
background: rgba(89, 214, 255, 0.12);
|
||||
color: #8be3ff;
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.dynamic-mode-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 10px 0;
|
||||
padding: 9px 11px;
|
||||
border: 1px solid rgba(126, 160, 194, 0.2);
|
||||
border-radius: 10px;
|
||||
color: #90a7bc;
|
||||
background: rgba(13, 27, 41, 0.55);
|
||||
}
|
||||
|
||||
.dynamic-mode-state b {
|
||||
margin-left: auto;
|
||||
color: #b9cad8;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.dynamic-mode-state.running {
|
||||
border-color: rgba(89, 214, 255, 0.48);
|
||||
color: #8be3ff;
|
||||
box-shadow: inset 0 0 20px rgba(89, 214, 255, 0.06);
|
||||
}
|
||||
|
||||
.dynamic-mode-state.running b {
|
||||
color: #e0f8ff;
|
||||
}
|
||||
|
||||
.hardware-access-field {
|
||||
padding: 12px;
|
||||
border: 1px solid rgba(38, 102, 159, 0.24);
|
||||
|
||||
Reference in New Issue
Block a user