当前任务等待设备检测
JOB —
@@ -234,6 +241,7 @@
const data = await response.json().catch(() => ({}));
if (!response.ok) throw new Error(data.message || data.error || `HTTP ${response.status}`);
render(data);
+ return data;
}
async function run(action, success = "") {
@@ -300,6 +308,15 @@
renderWorkflow(state.operation.phase);
renderAxes(state.axes, controller.job_active, transport.state === "online");
renderSeat(state.axes);
+ const seatViewer = $("#calibrationSeatViewer");
+ const axisControlEnabled = transport.state === "online" && !controller.job_active;
+ if (seatViewer) {
+ seatViewer.dataset.axisControlEnabled = String(axisControlEnabled);
+ }
+ $$('[data-seat-axis-label]').forEach((label) => {
+ label.dataset.controlEnabled = String(axisControlEnabled);
+ label.setAttribute("aria-disabled", String(!axisControlEnabled));
+ });
renderVideo(state.video);
$("#calibrationStartAll").disabled =
transport.state !== "online" || controller.job_active;
@@ -411,6 +428,47 @@
$("#calibrationTagLeg").textContent = `${legrest.physical_value.toFixed(1)}°`;
$("#calibrationTagBack").textContent = `${backrest.physical_value.toFixed(1)}°`;
$("#calibrationTagHeight").textContent = `${height.physical_value.toFixed(2)} cm`;
+ [
+ ["longitudinal", longitudinal],
+ ["legrest", legrest],
+ ["backrest", backrest],
+ ["height", height],
+ ].forEach(([name, axis]) => {
+ const label = $(`[data-seat-axis-label="${name}"]`);
+ if (!label) return;
+ label.setAttribute("aria-valuenow", String(Math.round(axis.permille)));
+ label.setAttribute("aria-valuetext", formatPhysical(axis, axis.permille));
+ });
+ }
+
+ function previewSeatAxis(event) {
+ if (!currentState?.axes?.length) return;
+ const viewer = $("#calibrationSeatViewer");
+ if (viewer?.dataset.axisControlEnabled !== "true") return;
+ const axisId = Number(event.detail?.axis);
+ const permille = Math.max(0, Math.min(1000, Number(event.detail?.permille)));
+ if (!Number.isInteger(axisId) || !Number.isFinite(permille)) return;
+ const axes = currentState.axes.map((axis) => ({ ...axis }));
+ const axis = axes.find((item) => item.id === axisId);
+ if (!axis) return;
+ axis.permille = permille;
+ axis.physical_value = axis.physical_range * permille / 1000;
+ const card = $(`.v2-calibration-axis-card[data-axis="${axisId}"]`);
+ if (card) {
+ const slider = $('input[type="range"]', card);
+ slider.value = String(permille);
+ $(".v2-calibration-axis-reading strong", card).textContent = formatPhysical(axis, permille);
+ }
+ renderSeat(axes);
+ }
+
+ function commitSeatAxis(event) {
+ const viewer = $("#calibrationSeatViewer");
+ if (viewer?.dataset.axisControlEnabled !== "true") return;
+ const axis = Number(event.detail?.axis);
+ const permille = Math.max(0, Math.min(1000, Math.round(Number(event.detail?.permille))));
+ if (!Number.isInteger(axis) || !Number.isFinite(permille)) return;
+ run(() => api("/move", { axis, permille }));
}
function renderVideo(video) {
@@ -542,7 +600,13 @@
closeEvents();
return;
}
- fetchState().catch((error) => toast(`自标定服务连接失败:${error.message}`, true));
+ fetchState().then(async (state) => {
+ if (bootstrapResetComplete) return;
+ bootstrapResetComplete = true;
+ if (state.transport?.mode === "simulation") {
+ await api("/session/reset");
+ }
+ }).catch((error) => toast(`自标定服务连接失败:${error.message}`, true));
connectEvents();
}
@@ -583,6 +647,8 @@
});
window.addEventListener("cognitive-agent:module-change", activate);
window.addEventListener("cognitive-agent:demo-role-change", activate);
+ window.addEventListener("calibration-seat-axis-preview", previewSeatAxis);
+ window.addEventListener("calibration-seat-axis-commit", commitSeatAxis);
window.addEventListener("beforeunload", closeEvents);
}
diff --git a/apps/web-console/self-extension.css b/apps/web-console/self-extension.css
index 3620b08..3b2e746 100644
--- a/apps/web-console/self-extension.css
+++ b/apps/web-console/self-extension.css
@@ -89,11 +89,245 @@
.console-v2[data-module="self-extension"] .v2-self-extension {
min-width: 0;
min-height: 0;
+ position: relative;
display: block;
grid-row: 2;
overflow: hidden;
}
+.v2-extension-pet {
+ --pet-x: 0px;
+ --pet-y: 0px;
+ width: 126px;
+ height: 126px;
+ position: absolute;
+ right: 18px;
+ bottom: 16px;
+ z-index: 24;
+ display: grid;
+ place-items: end center;
+ transform: translate3d(var(--pet-x), var(--pet-y), 0);
+ filter: drop-shadow(0 14px 16px rgba(9, 66, 63, 0.2));
+ touch-action: none;
+ user-select: none;
+}
+
+.v2-extension-pet-bubble {
+ max-width: 158px;
+ position: absolute;
+ right: 54px;
+ bottom: 89px;
+ padding: 7px 9px;
+ color: #285153;
+ background: rgba(255, 255, 255, 0.94);
+ border: 1px solid rgba(14, 119, 115, 0.18);
+ border-radius: 11px 11px 2px 11px;
+ box-shadow: 0 9px 24px rgba(22, 72, 70, 0.12);
+ font-size: 8px;
+ font-weight: 800;
+ line-height: 1.45;
+ opacity: 0;
+ transform: translateY(5px) scale(0.96);
+ transform-origin: right bottom;
+ transition: 170ms ease;
+ pointer-events: none;
+}
+
+.v2-extension-pet:hover .v2-extension-pet-bubble,
+.v2-extension-pet[data-mood="talking"] .v2-extension-pet-bubble,
+.v2-extension-pet[data-mood="happy"] .v2-extension-pet-bubble {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+}
+
+.v2-extension-pet-body {
+ width: 76px;
+ height: 77px;
+ position: relative;
+ display: block;
+ padding: 0;
+ color: inherit;
+ background: linear-gradient(145deg, #f4fffd 0%, #bfe9e5 54%, #7bc3bd 100%);
+ border: 2px solid rgba(7, 86, 82, 0.48);
+ border-radius: 25px 25px 22px 22px;
+ box-shadow:
+ inset 0 3px 0 rgba(255, 255, 255, 0.82),
+ inset 0 -6px 12px rgba(9, 93, 89, 0.13),
+ 0 6px 0 #326f6c;
+ cursor: grab;
+ animation: extension-pet-float 3.2s ease-in-out infinite;
+}
+
+.v2-extension-pet[data-dragging="true"] .v2-extension-pet-body {
+ cursor: grabbing;
+ animation-play-state: paused;
+}
+
+.v2-extension-pet-ear {
+ width: 22px;
+ height: 24px;
+ position: absolute;
+ top: -12px;
+ z-index: -1;
+ background: linear-gradient(145deg, #dff8f5, #75bbb6);
+ border: 2px solid rgba(7, 86, 82, 0.48);
+ border-radius: 7px 15px 5px 13px;
+}
+
+.v2-extension-pet-ear.left { left: 3px; transform: rotate(-20deg); }
+.v2-extension-pet-ear.right { right: 3px; transform: scaleX(-1) rotate(-20deg); }
+
+.v2-extension-pet-antenna {
+ width: 2px;
+ height: 17px;
+ position: absolute;
+ top: -21px;
+ left: 50%;
+ background: #397f7b;
+ transform: translateX(-50%);
+}
+
+.v2-extension-pet-antenna i {
+ width: 8px;
+ height: 8px;
+ position: absolute;
+ top: -5px;
+ left: -3px;
+ background: #ffbd62;
+ border: 1px solid #9f6630;
+ border-radius: 50%;
+ box-shadow: 0 0 0 4px rgba(255, 189, 98, 0.17);
+ animation: extension-pet-signal 1.8s ease-in-out infinite;
+}
+
+.v2-extension-pet-face {
+ width: 58px;
+ height: 34px;
+ position: absolute;
+ top: 9px;
+ left: 7px;
+ background: linear-gradient(180deg, #164e50, #0d383a);
+ border: 2px solid rgba(255, 255, 255, 0.65);
+ border-radius: 16px;
+ box-shadow: inset 0 4px 10px rgba(0, 0, 0, 0.2);
+}
+
+.v2-extension-pet-face .eye {
+ width: 8px;
+ height: 10px;
+ position: absolute;
+ top: 9px;
+ background: #bdfaf2;
+ border-radius: 50%;
+ box-shadow: 0 0 7px rgba(141, 255, 241, 0.72);
+ animation: extension-pet-blink 4.6s infinite;
+}
+
+.v2-extension-pet-face .eye.left { left: 12px; }
+.v2-extension-pet-face .eye.right { right: 12px; }
+
+.v2-extension-pet-face b {
+ width: 6px;
+ height: 4px;
+ position: absolute;
+ top: 19px;
+ left: 50%;
+ background: #ffc17a;
+ border-radius: 50% 50% 65% 65%;
+ transform: translateX(-50%);
+}
+
+.v2-extension-pet-face em {
+ width: 13px;
+ height: 6px;
+ position: absolute;
+ top: 22px;
+ left: 50%;
+ border-bottom: 2px solid #bdfaf2;
+ border-radius: 50%;
+ transform: translateX(-50%);
+}
+
+.v2-extension-pet[data-mood="happy"] .v2-extension-pet-face .eye {
+ height: 5px;
+ top: 13px;
+ background: transparent;
+ border-top: 2px solid #bdfaf2;
+ border-radius: 50%;
+}
+
+.v2-extension-pet-screen {
+ width: 36px;
+ height: 17px;
+ position: absolute;
+ bottom: 7px;
+ left: 18px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ color: #eafffb;
+ background: #277c78;
+ border: 1px solid rgba(6, 79, 75, 0.5);
+ border-radius: 7px;
+ font: 900 6px Consolas, monospace;
+}
+
+.v2-extension-pet-screen i {
+ width: 5px;
+ height: 5px;
+ background: #80f0bd;
+ border-radius: 50%;
+ box-shadow: 0 0 5px rgba(128, 240, 189, 0.8);
+}
+
+.v2-extension-pet-foot {
+ width: 21px;
+ height: 10px;
+ position: absolute;
+ bottom: -12px;
+ background: #5ba5a0;
+ border: 2px solid #326f6c;
+ border-radius: 4px 4px 9px 9px;
+}
+
+.v2-extension-pet-foot.left { left: 8px; }
+.v2-extension-pet-foot.right { right: 8px; }
+
+.v2-extension-pet-tail {
+ width: 27px;
+ height: 11px;
+ position: absolute;
+ right: -21px;
+ bottom: 12px;
+ z-index: -1;
+ border: 5px solid #4d9994;
+ border-left: 0;
+ border-radius: 0 13px 13px 0;
+ transform-origin: left center;
+ animation: extension-pet-tail 1.1s ease-in-out infinite alternate;
+}
+
+@keyframes extension-pet-float {
+ 0%, 100% { transform: translateY(0) rotate(-1deg); }
+ 50% { transform: translateY(-5px) rotate(1deg); }
+}
+
+@keyframes extension-pet-blink {
+ 0%, 45%, 49%, 100% { transform: scaleY(1); }
+ 47% { transform: scaleY(0.12); }
+}
+
+@keyframes extension-pet-signal {
+ 0%, 100% { opacity: 0.72; transform: scale(0.86); }
+ 50% { opacity: 1; transform: scale(1.15); }
+}
+
+@keyframes extension-pet-tail {
+ from { transform: rotate(-14deg); }
+ to { transform: rotate(16deg); }
+}
+
.v2-extension-sidebar > .v2-field-caption {
display: block;
margin: 0 0 8px;
diff --git a/apps/web-console/self-extension.js b/apps/web-console/self-extension.js
index 4793edd..1007a0b 100644
--- a/apps/web-console/self-extension.js
+++ b/apps/web-console/self-extension.js
@@ -278,7 +278,7 @@
const state = loadState();
const pages = Object.fromEntries(Object.keys(TOY_TYPES).map((key) => [key, 0]));
- let activeMode = sessionStorage.getItem(MODE_KEY) === "control" ? "control" : "study";
+ let activeMode = "study";
let selectedAddType = "light";
let learning = freshLearningSession();
let isPlanning = false;
@@ -290,6 +290,8 @@
let latestVehicleStateTurn = -1;
let sharedTwinBand = null;
let sharedTwinHome = null;
+ let petTimer = null;
+ let petPhraseIndex = 0;
function uid(prefix) {
if (globalThis.crypto?.randomUUID) return `${prefix}-${crypto.randomUUID()}`;
@@ -347,6 +349,14 @@
: {};
saved.selectedDeviceByType = saved.selectedDeviceByType || {};
saved.settings = { actionDelayMs: Number(saved.settings?.actionDelayMs) || 520 };
+ // Learned capabilities are durable configuration; execution lamps, history,
+ // and protocol feedback belong to one virtual run and must never survive a restart.
+ saved.history = [];
+ saved.lastProtocol = null;
+ saved.vehicleExecutions = {};
+ saved.sessionStartedAt = null;
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(saved));
+ sessionStorage.removeItem(MODE_KEY);
return saved;
} catch (error) {
console.warn("读取自扩展能力数据失败,已恢复默认状态。", error);
@@ -506,7 +516,18 @@
CH0 / CH1等待权威车端状态