添加红外扩展
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
"use strict";
|
||||
|
||||
const STORAGE_KEY = "cognitive-agent.ir-toy-console.v5";
|
||||
const SLOT_CATALOG_MIGRATION_KEY = "cognitive-agent.gscu-slot-catalog-migrated.v1";
|
||||
const MODE_KEY = "cognitive-agent.extension-mode";
|
||||
const CONVERSATION_KEY = "cognitive-agent.conversation-id";
|
||||
const SLOT_MIN = 1;
|
||||
@@ -451,6 +452,7 @@
|
||||
history: [],
|
||||
lastProtocol: null,
|
||||
vehicleExecutions: {},
|
||||
infraredCommands: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -471,6 +473,18 @@
|
||||
saved.vehicleExecutions = saved.vehicleExecutions && typeof saved.vehicleExecutions === "object"
|
||||
? saved.vehicleExecutions
|
||||
: {};
|
||||
saved.infraredCommands = Array.isArray(saved.infraredCommands)
|
||||
? saved.infraredCommands.filter((item) => (
|
||||
validSlot(item?.slot)
|
||||
&& typeof item?.name === "string"
|
||||
&& item.name.trim()
|
||||
)).map((item) => ({
|
||||
slot: Number(item.slot),
|
||||
name: item.name.trim().slice(0, 48),
|
||||
source: String(item.source || "tcp"),
|
||||
learnedAt: item.learnedAt || null,
|
||||
}))
|
||||
: [];
|
||||
saved.selectedDeviceByType = saved.selectedDeviceByType || {};
|
||||
saved.settings = { actionDelayMs: Number(saved.settings?.actionDelayMs) || 520 };
|
||||
// Learned capabilities are durable configuration; execution lamps, history,
|
||||
@@ -701,6 +715,39 @@
|
||||
<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>
|
||||
<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>
|
||||
<label><span>监听端口</span><input id="extensionGscuTcpPort" type="number" min="1" max="65535" value="4444" required /><small>必须与 GSCU1B 自定义 TCP 服务器端口一致。</small></label>
|
||||
<button class="v2-extension-primary" type="submit" id="extensionGscuTcpApply">应用并开始监听</button>
|
||||
</form>
|
||||
<section class="v2-gscu-actions">
|
||||
<header><div><strong>TCP 动作调试</strong><small>与 gscu1b_control.py tcp 使用相同 JSON 报文</small></div><label><span>红外槽位</span><input id="extensionGscuSlot" type="number" min="1" max="248" value="1" /></label><label><span>指令名称</span><input id="extensionGscuCommandName" maxlength="48" placeholder="例如:客厅空调开启" /></label></header>
|
||||
<div class="v2-gscu-action-grid">
|
||||
<button type="button" data-gscu-action="info">查询设备</button>
|
||||
<button type="button" data-gscu-action="learn">开始学习</button>
|
||||
<button type="button" data-gscu-action="cancel">取消学习</button>
|
||||
<button type="button" data-gscu-action="emit">发射槽位</button>
|
||||
<button type="button" data-gscu-action="read">读取槽位</button>
|
||||
<button type="button" data-gscu-action="restart">软重启</button>
|
||||
</div>
|
||||
<details class="v2-gscu-advanced">
|
||||
<summary>高级动作</summary>
|
||||
<label><span>原始红外码</span><textarea id="extensionGscuIrData" rows="3" placeholder="粘贴 read 返回的 ir_data"></textarea></label>
|
||||
<button type="button" data-gscu-action="write">写入当前槽位</button>
|
||||
<label><span>WiFi 配网锁</span><select id="extensionGscuWifiLock"><option value="1">开启(1)</option><option value="0">关闭(0)</option></select></label>
|
||||
<button type="button" data-gscu-action="wifi-lock">设置配网锁</button>
|
||||
<label><span>原始 JSON</span><textarea id="extensionGscuRawCommand" rows="4" placeholder='例如 {"type":"info"}'></textarea></label>
|
||||
<button type="button" data-gscu-action="raw">发送原始 JSON</button>
|
||||
<label><span>恢复出厂确认</span><input id="extensionGscuDangerConfirm" autocomplete="off" placeholder="恢复出厂请输入 RESET_DEVICE" /></label>
|
||||
<div><button class="danger" type="button" data-gscu-action="erase">擦除全部红外码</button><button class="danger" type="button" data-gscu-action="reset">恢复出厂设置</button></div>
|
||||
</details>
|
||||
<section class="v2-gscu-command-catalog"><header><strong>已学习红外信号</strong><small>槽位与名称保存在本机数据库,并按槽位号排列</small></header><div id="extensionGscuNamedCommands"></div></section>
|
||||
<pre id="extensionGscuTcpResult">等待操作</pre>
|
||||
</section>
|
||||
</dialog>
|
||||
<div class="v2-extension-toast-stack" id="extensionToastStack" aria-live="polite"></div>`;
|
||||
document.body.append(layer);
|
||||
}
|
||||
@@ -747,6 +794,319 @@
|
||||
return payload;
|
||||
}
|
||||
|
||||
function renderGscuTcpDialog(health = healthState || {}, refreshControls = true) {
|
||||
const status = $("#extensionGscuTcpStatus");
|
||||
if (!status) return;
|
||||
const connected = Boolean(health.connected || health.peer_connected);
|
||||
const listening = Boolean(health.listening);
|
||||
const listenHost = String(health.listen_host || "0.0.0.0");
|
||||
const listenPort = Number(health.port || 4444);
|
||||
if (refreshControls) {
|
||||
$("#extensionGscuTcpHost").value = listenHost;
|
||||
$("#extensionGscuTcpPort").value = Number.isInteger(listenPort) ? String(listenPort) : "4444";
|
||||
}
|
||||
status.dataset.state = connected ? "online" : listening ? "listening" : "offline";
|
||||
$("strong", status).textContent = connected
|
||||
? "GSCU1B TCP 已连接"
|
||||
: listening
|
||||
? "TCP 监听已就绪"
|
||||
: "TCP 监听尚未就绪";
|
||||
$("small", status).textContent = connected
|
||||
? `${health.peer || "设备会话"} → ${health.listen || `${listenHost}:${listenPort}`}`
|
||||
: health.message || `等待 GSCU1B 接入 ${listenHost}:${listenPort}`;
|
||||
$("b", status).textContent = connected ? "READY" : listening ? "WAIT" : "OFFLINE";
|
||||
if (refreshControls) {
|
||||
renderGscuNamedCommands();
|
||||
syncGscuCommandNameFromSlot();
|
||||
}
|
||||
}
|
||||
|
||||
function namedInfraredCommands() {
|
||||
state.infraredCommands ||= [];
|
||||
return [...state.infraredCommands].sort((left, right) => Number(left.slot) - Number(right.slot));
|
||||
}
|
||||
|
||||
function normalizedInfraredSlotRecord(item) {
|
||||
return {
|
||||
slot: Number(item.slot),
|
||||
name: String(item.name || "").trim().slice(0, 48),
|
||||
source: String(item.source || "tcp"),
|
||||
learnedAt: item.learned_at || item.learnedAt || null,
|
||||
updatedAt: item.updated_at || item.updatedAt || null,
|
||||
};
|
||||
}
|
||||
|
||||
function replaceInfraredSlotCatalog(items) {
|
||||
state.infraredCommands = (Array.isArray(items) ? items : [])
|
||||
.filter((item) => validSlot(item?.slot) && String(item?.name || "").trim())
|
||||
.map(normalizedInfraredSlotRecord)
|
||||
.sort((left, right) => left.slot - right.slot);
|
||||
saveState();
|
||||
renderGscuNamedCommands();
|
||||
syncGscuCommandNameFromSlot();
|
||||
}
|
||||
|
||||
async function refreshInfraredSlotCatalog({ migrateLegacy = true } = {}) {
|
||||
const shouldMigrate = migrateLegacy
|
||||
&& localStorage.getItem(SLOT_CATALOG_MIGRATION_KEY) !== "done";
|
||||
const legacy = shouldMigrate ? namedInfraredCommands() : [];
|
||||
let result = await api("gscu/slots");
|
||||
const serverSlots = Array.isArray(result.slots) ? result.slots : [];
|
||||
const existingSlots = new Set(serverSlots.map((item) => Number(item.slot)));
|
||||
const missing = legacy.filter((item) => !existingSlots.has(Number(item.slot)));
|
||||
if (missing.length) {
|
||||
await Promise.all(missing.map((item) => api(`gscu/slots/${item.slot}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ name: item.name, source: item.source || "browser-migration" }),
|
||||
})));
|
||||
result = await api("gscu/slots");
|
||||
}
|
||||
if (shouldMigrate) localStorage.setItem(SLOT_CATALOG_MIGRATION_KEY, "done");
|
||||
replaceInfraredSlotCatalog(result.slots);
|
||||
return namedInfraredCommands();
|
||||
}
|
||||
|
||||
async function upsertNamedInfraredCommand(slot, name, source = "tcp") {
|
||||
const normalizedSlot = Number(slot);
|
||||
const normalizedName = String(name || "").trim().slice(0, 48);
|
||||
if (!validSlot(normalizedSlot)) throw new Error("红外槽位范围是 1–248。");
|
||||
if (!normalizedName) throw new Error("学习红外指令前需要填写指令名称。");
|
||||
const result = await api(`gscu/slots/${normalizedSlot}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ name: normalizedName, source }),
|
||||
});
|
||||
const value = normalizedInfraredSlotRecord(result.slot);
|
||||
const existing = state.infraredCommands.find((item) => item.slot === normalizedSlot);
|
||||
if (existing) Object.assign(existing, value);
|
||||
else state.infraredCommands.push(value);
|
||||
saveState();
|
||||
renderGscuNamedCommands();
|
||||
return value;
|
||||
}
|
||||
|
||||
function renderGscuNamedCommands() {
|
||||
const container = $("#extensionGscuNamedCommands");
|
||||
if (!container) return;
|
||||
const commands = namedInfraredCommands();
|
||||
container.innerHTML = commands.length
|
||||
? commands.map((command) => `<article data-gscu-command-slot="${command.slot}"><header><span>红外槽位</span><b>IR ${String(command.slot).padStart(3, "0")}</b></header><label><span>信号名称</span><input maxlength="48" value="${escapeHtml(command.name)}" aria-label="IR ${command.slot} 信号名称" /></label><footer><button type="button" data-gscu-named-emit="${command.slot}">发射</button><button type="button" data-gscu-named-save="${command.slot}">保存名称</button><button class="danger" type="button" data-gscu-named-remove="${command.slot}">删除本地记录</button></footer></article>`).join("")
|
||||
: "<p>学习成功后将在这里显示编号与指令名称。</p>";
|
||||
}
|
||||
|
||||
function syncGscuCommandNameFromSlot() {
|
||||
const slot = Number($("#extensionGscuSlot")?.value);
|
||||
const command = namedInfraredCommands().find((item) => item.slot === slot);
|
||||
if ($("#extensionGscuCommandName")) {
|
||||
$("#extensionGscuCommandName").value = command?.name || "";
|
||||
}
|
||||
}
|
||||
|
||||
function openGscuTcpDialog(health = healthState || {}) {
|
||||
renderGscuTcpDialog(health);
|
||||
openDialog("extensionGscuTcpDialog");
|
||||
void refreshInfraredSlotCatalog().catch((error) => {
|
||||
showToast(`读取本地红外槽位数据库失败:${error.message}`, "error");
|
||||
});
|
||||
window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } });
|
||||
}
|
||||
|
||||
function showGscuResult(value) {
|
||||
const output = $("#extensionGscuTcpResult");
|
||||
if (!output) return;
|
||||
output.textContent = typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
||||
}
|
||||
|
||||
async function configureGscuTcp(event) {
|
||||
event.preventDefault();
|
||||
const button = $("#extensionGscuTcpApply");
|
||||
button.disabled = true;
|
||||
button.textContent = "正在重启监听…";
|
||||
try {
|
||||
const result = await api("gscu/config", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
listen_host: $("#extensionGscuTcpHost").value.trim(),
|
||||
port: Number($("#extensionGscuTcpPort").value),
|
||||
}),
|
||||
});
|
||||
healthState = result;
|
||||
renderGscuTcpDialog(result, false);
|
||||
showGscuResult(result);
|
||||
showToast(`TCP 已切换到 ${result.listen || `${result.listen_host}:${result.port}`}`, "success");
|
||||
} catch (error) {
|
||||
showGscuResult(error.payload || error.message);
|
||||
showToast(error.message, "error");
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
button.textContent = "应用并开始监听";
|
||||
}
|
||||
}
|
||||
|
||||
async function runGscuTcpAction(button) {
|
||||
const action = button.dataset.gscuAction;
|
||||
const slot = Number($("#extensionGscuSlot").value);
|
||||
const commandName = $("#extensionGscuCommandName").value.trim();
|
||||
const payload = {};
|
||||
if (["learn", "emit", "read", "write"].includes(action)) payload.slot = slot;
|
||||
if (action === "write") payload.ir_data = $("#extensionGscuIrData").value.trim();
|
||||
if (action === "wifi-lock") payload.state = Number($("#extensionGscuWifiLock").value);
|
||||
if (action === "raw") {
|
||||
try {
|
||||
payload.command = JSON.parse($("#extensionGscuRawCommand").value);
|
||||
} catch (_error) {
|
||||
showToast("原始指令不是有效 JSON。", "error");
|
||||
return;
|
||||
}
|
||||
}
|
||||
const confirmation = $("#extensionGscuDangerConfirm").value.trim();
|
||||
if (action === "erase") {
|
||||
const count = namedInfraredCommands().length;
|
||||
const confirmed = window.confirm(
|
||||
`确认擦除 GSCU1B 中的全部红外码吗?本机数据库中的 ${count} 条槽位记录也会被永久清空,操作无法恢复。`,
|
||||
);
|
||||
if (!confirmed) return;
|
||||
payload.confirm = "ERASE_ALL";
|
||||
}
|
||||
if (action === "reset") payload.confirm = confirmation;
|
||||
if (action === "learn" && !commandName) {
|
||||
showToast("请输入该红外编号对应的指令名称。", "error");
|
||||
$("#extensionGscuCommandName").focus();
|
||||
return;
|
||||
}
|
||||
button.disabled = true;
|
||||
showGscuResult(`正在执行 ${action}…`);
|
||||
try {
|
||||
const endpoint = action === "erase" ? "gscu/erase-all" : `gscu/${action}`;
|
||||
let result = await api(endpoint, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (action === "learn") {
|
||||
showToast("学习指令已下发,请立即短按遥控器目标按键。", "success");
|
||||
let readResult = null;
|
||||
let learnedSignal = null;
|
||||
let readError = null;
|
||||
await delay(1200);
|
||||
for (let attempt = 0; attempt < 6 && !learnedSignal; attempt += 1) {
|
||||
try {
|
||||
readResult = await api("gscu/read", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ slot }),
|
||||
});
|
||||
learnedSignal = infraredSignalFromResponse(readResult);
|
||||
} catch (error) {
|
||||
readError = error;
|
||||
}
|
||||
if (!learnedSignal && attempt < 5) await delay(1400);
|
||||
}
|
||||
if (!learnedSignal) {
|
||||
throw new Error(
|
||||
readError?.message
|
||||
? `学习指令已下发,但槽位回读失败:${readError.message}`
|
||||
: "学习指令已下发,但槽位回读为空;请重新学习并短按遥控器按键。",
|
||||
);
|
||||
}
|
||||
await upsertNamedInfraredCommand(slot, commandName, "tcp");
|
||||
result = {
|
||||
...result,
|
||||
learning_capture_confirmed: true,
|
||||
verification: "slot_readback",
|
||||
slot_readback: readResult,
|
||||
};
|
||||
}
|
||||
if (action === "erase") {
|
||||
replaceInfraredSlotCatalog([]);
|
||||
state.capabilities.forEach((capability) => {
|
||||
if (!validSlot(capability.slot)) return;
|
||||
Object.assign(capability, {
|
||||
slot: null,
|
||||
status: "pending",
|
||||
verified: false,
|
||||
enabled: false,
|
||||
fingerprint: null,
|
||||
signalBytes: null,
|
||||
learnedAt: null,
|
||||
relearnedAt: null,
|
||||
});
|
||||
});
|
||||
saveState();
|
||||
renderAll();
|
||||
void syncPeripheralCatalog();
|
||||
}
|
||||
showGscuResult(result);
|
||||
showToast(
|
||||
action === "learn" ? `IR ${slot} 学习完成,槽位回读已确认。` : `${action} 指令处理完成。`,
|
||||
"success",
|
||||
);
|
||||
} catch (error) {
|
||||
showGscuResult(error.payload || error.message);
|
||||
showToast(error.message, "error");
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function bindGscuTcpDialogEvents() {
|
||||
const dialog = $("#extensionGscuTcpDialog");
|
||||
if (!dialog || dialog.dataset.bound === "true") return;
|
||||
dialog.dataset.bound = "true";
|
||||
$("#extensionGscuTcpForm").addEventListener("submit", configureGscuTcp);
|
||||
$("#extensionGscuSlot").addEventListener("change", syncGscuCommandNameFromSlot);
|
||||
dialog.addEventListener("click", (event) => {
|
||||
if (event.target.closest('[data-close-extension-dialog="extensionGscuTcpDialog"]')) {
|
||||
closeDialog("extensionGscuTcpDialog");
|
||||
return;
|
||||
}
|
||||
const action = event.target.closest("[data-gscu-action]");
|
||||
if (action) {
|
||||
void runGscuTcpAction(action);
|
||||
return;
|
||||
}
|
||||
const emit = event.target.closest("[data-gscu-named-emit]");
|
||||
if (emit) {
|
||||
$("#extensionGscuSlot").value = emit.dataset.gscuNamedEmit;
|
||||
const command = namedInfraredCommands().find(
|
||||
(item) => item.slot === Number(emit.dataset.gscuNamedEmit),
|
||||
);
|
||||
$("#extensionGscuCommandName").value = command?.name || "";
|
||||
const emitButton = $("[data-gscu-action=\"emit\"]", dialog);
|
||||
if (emitButton) void runGscuTcpAction(emitButton);
|
||||
return;
|
||||
}
|
||||
const save = event.target.closest("[data-gscu-named-save]");
|
||||
if (save) {
|
||||
const card = save.closest("[data-gscu-command-slot]");
|
||||
save.disabled = true;
|
||||
void upsertNamedInfraredCommand(
|
||||
Number(save.dataset.gscuNamedSave),
|
||||
$("input", card).value,
|
||||
"tcp",
|
||||
).then((value) => {
|
||||
showToast(`IR ${value.slot} 已保存为“${value.name}”。`, "success");
|
||||
}).catch((error) => {
|
||||
showToast(error.message, "error");
|
||||
}).finally(() => { save.disabled = false; });
|
||||
return;
|
||||
}
|
||||
const remove = event.target.closest("[data-gscu-named-remove]");
|
||||
if (remove) {
|
||||
const slot = Number(remove.dataset.gscuNamedRemove);
|
||||
const command = namedInfraredCommands().find((item) => item.slot === slot);
|
||||
if (!window.confirm(`确认删除“${command?.name || `IR ${slot}`}”的本地数据库记录吗?GSCU1B 设备中的该红外码不会被擦除。`)) return;
|
||||
remove.disabled = true;
|
||||
void api(`gscu/slots/${slot}`, { method: "DELETE" }).then((result) => {
|
||||
state.infraredCommands = namedInfraredCommands().filter((item) => item.slot !== slot);
|
||||
saveState();
|
||||
renderGscuNamedCommands();
|
||||
showToast(result.message || `IR ${slot} 的本地记录已删除。`, "success");
|
||||
}).catch((error) => {
|
||||
remove.disabled = false;
|
||||
showToast(error.message, "error");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sharedConversationId() {
|
||||
const fromConsole = window.CognitiveAgentConsole?.getConversationId?.();
|
||||
if (typeof fromConsole === "string" && fromConsole.startsWith("conv-")) {
|
||||
@@ -1224,22 +1584,22 @@
|
||||
);
|
||||
let learnedSignal = null;
|
||||
let readError = null;
|
||||
await delay(700);
|
||||
for (let attempt = 0; attempt < 3 && !learnedSignal; attempt += 1) {
|
||||
await delay(1200);
|
||||
for (let attempt = 0; attempt < 6 && !learnedSignal; attempt += 1) {
|
||||
try {
|
||||
const readResult = await sendGatewayCommand("read", { slot: learning.draft.slot });
|
||||
learnedSignal = infraredSignalFromResponse(readResult);
|
||||
} catch (error) {
|
||||
readError = error;
|
||||
}
|
||||
if (!learnedSignal && attempt < 2) await delay(900);
|
||||
if (!learnedSignal && attempt < 5) await delay(1400);
|
||||
}
|
||||
learnedSignal ||= infraredSignalFromResponse(learnResult);
|
||||
if (!learnedSignal) {
|
||||
throw new Error(
|
||||
readError?.message
|
||||
? `学习后无法回读槽位:${readError.message}`
|
||||
: "学习回执成功,但物理槽位回读为空;请对准学习器并短按遥控器按键。",
|
||||
: "学习指令已下发,但物理槽位回读为空;请对准学习器并短按遥控器按键。",
|
||||
);
|
||||
}
|
||||
learning.fingerprint = await infraredFingerprint(learnedSignal);
|
||||
@@ -1295,14 +1655,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
function confirmLearning() {
|
||||
async function confirmLearning() {
|
||||
if (!learning.tested || !learning.draft) return;
|
||||
const capability = state.capabilities.find(
|
||||
(item) => item.id === learning.draft.capabilityId,
|
||||
);
|
||||
if (!capability) return;
|
||||
const button = $("#extensionConfirmLearn");
|
||||
button.disabled = true;
|
||||
const relearning = Boolean(learning.draft.relearning);
|
||||
const learnedAt = new Date().toISOString();
|
||||
try {
|
||||
await upsertNamedInfraredCommand(
|
||||
Number(learning.draft.slot),
|
||||
capability.name,
|
||||
"capability",
|
||||
);
|
||||
} catch (error) {
|
||||
button.disabled = false;
|
||||
showToast(`保存红外槽位数据库失败:${error.message}`, "error");
|
||||
return false;
|
||||
}
|
||||
Object.assign(capability, {
|
||||
aliases: [...new Set([capability.name, ...learning.draft.aliases])],
|
||||
risk: learning.draft.risk,
|
||||
@@ -1327,6 +1700,7 @@
|
||||
"success",
|
||||
);
|
||||
learning = freshLearningSession();
|
||||
return true;
|
||||
}
|
||||
|
||||
function forgetCapability(capabilityId) {
|
||||
@@ -1796,6 +2170,7 @@
|
||||
try {
|
||||
await sendGatewayCommand("erase", { confirm: "ERASE_ALL" });
|
||||
state.capabilities = state.devices.flatMap((device) => seedPresets(device));
|
||||
state.infraredCommands = [];
|
||||
state.history = [];
|
||||
state.sessionStartedAt = new Date().toISOString();
|
||||
saveState();
|
||||
@@ -2276,6 +2651,7 @@
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
bindGscuTcpDialogEvents();
|
||||
$$('[data-extension-mode]').forEach((button) => {
|
||||
button.addEventListener("click", () => setMode(button.dataset.extensionMode));
|
||||
});
|
||||
@@ -2330,7 +2706,7 @@
|
||||
$("#extensionStartLearn").addEventListener("click", startLearning);
|
||||
$("#extensionCancelLearn").addEventListener("click", cancelLearning);
|
||||
$("#extensionTestEmit").addEventListener("click", testLearning);
|
||||
$("#extensionConfirmLearn").addEventListener("click", confirmLearning);
|
||||
$("#extensionConfirmLearn").addEventListener("click", () => { void confirmLearning(); });
|
||||
$("#extensionLearnDialog").addEventListener("cancel", (event) => {
|
||||
if (learning.phase === "listening") {
|
||||
event.preventDefault();
|
||||
@@ -2900,7 +3276,9 @@
|
||||
function bindUnifiedNfcEvents(mount) {
|
||||
mount.addEventListener("click", (event) => {
|
||||
if (event.target.closest("[data-open-nfc-learning]")) openNfcDialog();
|
||||
if (event.target.closest("[data-open-gscu-tcp]")) openGscuTcpDialog();
|
||||
});
|
||||
bindGscuTcpDialogEvents();
|
||||
const dialog = $("#extensionNfcDialog");
|
||||
dialog.addEventListener("click", (event) => {
|
||||
if (event.target.closest("[data-close-nfc-dialog]")) closeDialog("extensionNfcDialog");
|
||||
@@ -2926,9 +3304,10 @@
|
||||
$("#extensionStartLearn").addEventListener("click", startLearning);
|
||||
$("#extensionCancelLearn").addEventListener("click", cancelLearning);
|
||||
$("#extensionTestEmit").addEventListener("click", testLearning);
|
||||
$("#extensionConfirmLearn").addEventListener("click", () => {
|
||||
confirmLearning();
|
||||
void syncPeripheralCatalog(linkedDeviceForCard(nfcSnapshot.card?.uid));
|
||||
$("#extensionConfirmLearn").addEventListener("click", async () => {
|
||||
if (await confirmLearning()) {
|
||||
void syncPeripheralCatalog(linkedDeviceForCard(nfcSnapshot.card?.uid));
|
||||
}
|
||||
});
|
||||
$("#extensionLearnDialog").addEventListener("cancel", (event) => {
|
||||
if (learning.phase === "listening") {
|
||||
@@ -2953,6 +3332,8 @@
|
||||
|
||||
const render = () => {
|
||||
const health = snapshot?.extension_health || {};
|
||||
healthState = health;
|
||||
if ($("#extensionGscuTcpDialog")?.open) renderGscuTcpDialog(health, false);
|
||||
const embeddedNfc = health.nfc;
|
||||
if (nfcSnapshot.revision < 0 && embeddedNfc) {
|
||||
nfcSnapshot = {
|
||||
@@ -3061,6 +3442,7 @@
|
||||
<span><i data-lucide="usb"></i></span>
|
||||
<div><strong>${gatewayLabel}</strong><small>${escapeHtml(gatewayNote)}</small></div>
|
||||
<b>${String(health.transport || "GSCU").toUpperCase()}</b>
|
||||
<button type="button" data-open-gscu-tcp><i data-lucide="settings-2"></i><span>TCP 设置</span></button>
|
||||
</div>
|
||||
<div class="v2-extension-device-list">
|
||||
${devices.length ? devices.map((device) => `
|
||||
@@ -3090,6 +3472,9 @@
|
||||
buildDialogs();
|
||||
buildUnifiedNfcDialog();
|
||||
bindUnifiedNfcEvents(mount);
|
||||
void refreshInfraredSlotCatalog().catch((error) => {
|
||||
console.warn("读取本地红外槽位数据库失败。", error);
|
||||
});
|
||||
render();
|
||||
renderNfcDialog();
|
||||
startNfcPolling();
|
||||
@@ -3109,6 +3494,9 @@
|
||||
setMode(activeMode);
|
||||
if (document.body.dataset.module === "self-extension") refreshHealth();
|
||||
startHealthPolling();
|
||||
void refreshInfraredSlotCatalog().catch((error) => {
|
||||
console.warn("读取本地红外槽位数据库失败。", error);
|
||||
});
|
||||
void migrateLegacyProfiles();
|
||||
window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user