This commit is contained in:
vee
2026-08-14 15:01:29 +08:00
parent 6d768aaef4
commit f77dda70fe
20 changed files with 1125 additions and 122 deletions
+109 -2
View File
@@ -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 @@
<span><i>CH0 / CH1</i>等待权威车端状态</span>
</div>
<div class="v2-extension-run-plan" id="extensionRunPlan" hidden></div>
</section>`;
</section>
<aside class="v2-extension-pet" id="extensionDesktopPet" data-mood="idle" aria-live="polite">
<span class="v2-extension-pet-bubble" id="extensionPetBubble">嗨,我会陪你学习新能力</span>
<button class="v2-extension-pet-body" id="extensionPetBody" type="button" aria-label="网页版桌宠,拖动可移动,点击可互动">
<span class="v2-extension-pet-antenna"><i></i></span>
<span class="v2-extension-pet-ear left"></span><span class="v2-extension-pet-ear right"></span>
<span class="v2-extension-pet-face"><i class="eye left"></i><i class="eye right"></i><b></b><em></em></span>
<span class="v2-extension-pet-screen"><i></i><b>EXT</b></span>
<span class="v2-extension-pet-foot left"></span><span class="v2-extension-pet-foot right"></span>
<span class="v2-extension-pet-tail"></span>
</button>
</aside>`;
moduleSwitcher.insertAdjacentElement("afterend", root);
buildDialogs();
@@ -1603,6 +1624,91 @@
}
}
function initializeDesktopPet() {
const pet = $("#extensionDesktopPet");
const body = $("#extensionPetBody");
const bubble = $("#extensionPetBubble");
const host = $("#v2SelfExtension");
if (!pet || !body || !bubble || !host) return;
const phrases = [
"嗨,我会陪你学习新能力",
"拖动我可以换个位置",
"玩具动作和车端设备都能一起编排",
"虚拟运行重启后,我也会恢复待命",
"点击车端动作,我会帮你盯着执行状态",
];
const pointer = { active: false, moved: false, x: 0, y: 0, startX: 0, startY: 0 };
const say = (message, mood = "talking") => {
bubble.textContent = message;
pet.dataset.mood = mood;
window.clearTimeout(Number(pet.dataset.moodTimer || 0));
pet.dataset.moodTimer = String(window.setTimeout(() => {
pet.dataset.mood = "idle";
}, 2600));
};
const clampPosition = (x, y) => {
const hostRect = host.getBoundingClientRect();
const petRect = pet.getBoundingClientRect();
return {
x: Math.max(-hostRect.width + petRect.width + 10, Math.min(0, x)),
y: Math.max(-hostRect.height + petRect.height + 10, Math.min(0, y)),
};
};
body.addEventListener("pointerdown", (event) => {
if (event.button !== 0) return;
pointer.active = true;
pointer.moved = false;
pointer.startX = event.clientX;
pointer.startY = event.clientY;
pointer.x = Number(pet.dataset.x || 0);
pointer.y = Number(pet.dataset.y || 0);
body.setPointerCapture?.(event.pointerId);
pet.dataset.dragging = "true";
});
body.addEventListener("pointermove", (event) => {
if (!pointer.active) return;
const deltaX = event.clientX - pointer.startX;
const deltaY = event.clientY - pointer.startY;
pointer.moved ||= Math.hypot(deltaX, deltaY) > 4;
const position = clampPosition(pointer.x + deltaX, pointer.y + deltaY);
pet.dataset.x = String(position.x);
pet.dataset.y = String(position.y);
pet.style.setProperty("--pet-x", `${position.x}px`);
pet.style.setProperty("--pet-y", `${position.y}px`);
});
const endDrag = () => {
pointer.active = false;
delete pet.dataset.dragging;
};
body.addEventListener("pointerup", endDrag);
body.addEventListener("pointercancel", endDrag);
body.addEventListener("click", () => {
if (pointer.moved) return;
petPhraseIndex = (petPhraseIndex + 1) % phrases.length;
say(phrases[petPhraseIndex], petPhraseIndex % 2 ? "happy" : "talking");
});
window.addEventListener("resize", () => {
const position = clampPosition(Number(pet.dataset.x || 0), Number(pet.dataset.y || 0));
pet.dataset.x = String(position.x);
pet.dataset.y = String(position.y);
pet.style.setProperty("--pet-x", `${position.x}px`);
pet.style.setProperty("--pet-y", `${position.y}px`);
});
window.addEventListener("cognitive-agent:module-change", (event) => {
if (event.detail?.module === "self-extension") say("自扩展工作台已就绪", "happy");
});
petTimer = window.setInterval(() => {
if (document.visibilityState !== "visible"
|| document.body.dataset.module !== "self-extension") return;
petPhraseIndex = (petPhraseIndex + 1) % phrases.length;
say(phrases[petPhraseIndex]);
}, 14000);
window.addEventListener("pagehide", () => window.clearInterval(petTimer));
}
function bindEvents() {
$$('[data-extension-mode]').forEach((button) => {
button.addEventListener("click", () => setMode(button.dataset.extensionMode));
@@ -1723,6 +1829,7 @@
function init() {
if (!buildShell()) return;
initializeDesktopPet();
bindEvents();
renderAll();
setMode(activeMode);