feat(n7web): sync N7 scene cards and optimize runtime

This commit is contained in:
Miayui
2026-09-17 11:04:05 +08:00
parent 75ce391312
commit 7d196db02b
31 changed files with 2924 additions and 126 deletions
+132 -5
View File
@@ -389,6 +389,11 @@
let selectedPetCardId = null;
const petCardDrafts = new Map();
let nfcPollTimer = null;
let nfcDialogFingerprint = "";
let nfcDialogDirty = true;
let unifiedExtensionFingerprint = "";
let unifiedExtensionDirty = true;
let gscuHealthFingerprint = "";
let legacySameOriginChecked = false;
let legacyMigrationCompleted = false;
const legacyBridgeAttempts = new Set();
@@ -3673,12 +3678,50 @@
return reader?.error || "请连接 NFC 读卡器并确认 CH340 / CH341 驱动可用";
}
function renderNfcDialog() {
function currentNfcDialogFingerprint() {
return JSON.stringify({
revision: nfcSnapshot.revision,
reader: nfcSnapshot.reader,
card: nfcSnapshot.card,
petRevision: petCardSnapshot.revision,
pets: petCardSnapshot.cards,
publisher: petCardSnapshot.publisher,
devices: state.devices.map((device) => ({
id: device.id,
name: device.name,
type: device.type,
cardUid: device.cardUid || null,
})),
capabilities: state.capabilities.map((capability) => ({
id: capability.id,
deviceId: capability.deviceId,
name: capability.name,
aliases: capability.aliases,
slot: capability.slot,
status: capability.status,
verified: capability.verified,
enabled: capability.enabled !== false,
})),
infraredCommands: state.infraredCommands,
});
}
function renderNfcDialog({ force = false } = {}) {
const dialog = $("#extensionNfcDialog");
if (!dialog) return false;
if (!force && !dialog.open) {
nfcDialogDirty = true;
return false;
}
const renderFingerprint = currentNfcDialogFingerprint();
if (!force && !nfcDialogDirty && renderFingerprint === nfcDialogFingerprint) {
return false;
}
const stage = $("#extensionNfcStage");
const body = $("#extensionNfcBody");
const status = $("#extensionNfcReaderStatus");
const badge = $("#extensionNfcReaderBadge");
if (!stage || !body || !status || !badge) return;
if (!stage || !body || !status || !badge) return false;
const reader = nfcSnapshot.reader || {};
const card = nfcSnapshot.card;
@@ -3725,7 +3768,10 @@
${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>`;
}
nfcDialogFingerprint = renderFingerprint;
nfcDialogDirty = false;
window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } });
return true;
}
function buildUnifiedNfcDialog() {
@@ -3891,8 +3937,8 @@
}
function openNfcDialog() {
renderNfcDialog();
openDialog("extensionNfcDialog");
renderNfcDialog({ force: true });
void refreshNfcStatus();
}
@@ -3969,10 +4015,73 @@
if (!mount) return false;
let snapshot = null;
const render = () => {
const extensionViewFingerprint = (health) => JSON.stringify({
transport: health.transport,
connected: health.connected,
peerConnected: health.peer_connected,
peer: health.peer,
listening: health.listening,
message: health.message,
extensionCatalog: (snapshot?.extension?.catalog || []).map((item) => ({
id: item.id,
deviceId: item.device_id,
deviceName: item.device_name,
deviceType: item.device_type,
typeLabel: item.type_label,
action: item.action,
name: item.name,
cardUid: item.card_uid,
status: item.status,
enabled: item.enabled,
})),
lastAction: snapshot?.extension?.last_action ? {
command: snapshot.extension.last_action.command,
type: snapshot.extension.last_action.type,
} : null,
lastResult: snapshot?.extension?.result ? {
status: snapshot.extension.result.status,
message: snapshot.extension.result.message,
} : null,
nfc: {
revision: nfcSnapshot.revision,
reader: nfcSnapshot.reader,
card: nfcSnapshot.card,
},
pets: petCardSnapshot,
localDevices: state.devices.map((device) => ({
id: device.id,
name: device.name,
type: device.type,
cardUid: device.cardUid || null,
})),
localCapabilities: state.capabilities.map((capability) => ({
id: capability.id,
deviceId: capability.deviceId,
name: capability.name,
slot: capability.slot,
status: capability.status,
verified: capability.verified,
enabled: capability.enabled !== false,
})),
infraredCommands: state.infraredCommands,
});
const render = ({ force = false } = {}) => {
const health = snapshot?.extension_health || {};
healthState = health;
if ($("#extensionGscuTcpDialog")?.open) renderGscuTcpDialog(health, false);
const nextGscuHealthFingerprint = JSON.stringify({
transport: health.transport,
connected: health.connected,
peerConnected: health.peer_connected,
peer: health.peer,
listening: health.listening,
message: health.message,
});
if ($("#extensionGscuTcpDialog")?.open
&& (force || nextGscuHealthFingerprint !== gscuHealthFingerprint)) {
renderGscuTcpDialog(health, false);
}
gscuHealthFingerprint = nextGscuHealthFingerprint;
const embeddedNfc = health.nfc;
if (nfcSnapshot.revision < 0 && embeddedNfc) {
nfcSnapshot = {
@@ -3989,6 +4098,16 @@
}
}
if (health.pet_cards) applyPetCardSnapshot(health.pet_cards, { render: false });
const panel = mount.closest("#v2ExtensionPanel");
if (!force && (panel?.hidden || panel?.classList.contains("is-collapsed"))) {
unifiedExtensionDirty = true;
return false;
}
const renderFingerprint = extensionViewFingerprint(health);
if (!force
&& !unifiedExtensionDirty
&& renderFingerprint === unifiedExtensionFingerprint
&& mount.childElementCount) return false;
const physicalConnected = health.transport !== "simulation"
&& Boolean(health.connected || health.peer_connected);
const listening = Boolean(health.listening);
@@ -4114,7 +4233,10 @@
<i data-lucide="workflow"></i><div><small>最近联动</small><strong>${escapeHtml(visibleLastAction?.command || visibleLastAction?.type || "等待 NFC 外设感应")}</strong></div>
<span>${escapeHtml(visibleLastAction ? (lastResult?.status || lastResult?.message || "STANDBY") : "STANDBY")}</span>
</footer>`;
unifiedExtensionFingerprint = renderFingerprint;
unifiedExtensionDirty = false;
window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } });
return true;
};
renderUnifiedExtensionView = render;
@@ -4125,6 +4247,11 @@
}
render();
});
window.addEventListener("cognitive-agent:side-panel-visibility", (event) => {
if (event.detail?.panel === "extensions" && event.detail?.visible) {
render({ force: true });
}
});
buildDialogs();
buildUnifiedNfcDialog();
bindUnifiedNfcEvents(mount);