import { DEFAULT_DESCRIPTOR, buildManualCommand, cloneDescriptor, parseInstruction, splitAliases, } from "./parser-engine.js"; const $ = (selector, root = document) => root.querySelector(selector); const $$ = (selector, root = document) => [...root.querySelectorAll(selector)]; let descriptor = cloneDescriptor(); let latestPayload = null; const categoryLabels = { vehicle_electrical: "车端电器", seat: "座椅设备", peripheral: "扩展外设", }; const transportLabels = { native_can: "原生 CAN", domain_runtime: "领域执行器", gscu_tcp: "红外网关", mqtt: "MQTT", }; const exampleTemplates = [ (name) => `开启${name}`, (name) => `把${name}温度调到4度`, (name) => `将${name}设为速冷模式`, (name) => `关闭${name}`, (name) => `把${name}调到零下10度`, ]; function escapeHtml(value) { return String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function fillIdentityForm() { $("#deviceName").value = descriptor.device.name; $("#deviceId").value = descriptor.device.id; $("#deviceDomain").value = descriptor.device.domain; $("#deviceCategory").value = descriptor.device.category; $("#deviceAliases").value = descriptor.device.aliases.join("、"); $("#deviceScope").value = descriptor.device.defaultScope; $("#deviceTransport").value = descriptor.device.transport; } function syncIdentityFromForm() { descriptor.device.name = $("#deviceName").value.trim() || "未命名设备"; descriptor.device.id = $("#deviceId").value.trim() || "unnamed_device"; descriptor.device.domain = $("#deviceDomain").value.trim() || "custom"; descriptor.device.category = $("#deviceCategory").value; descriptor.device.aliases = splitAliases($("#deviceAliases").value); descriptor.device.defaultScope = $("#deviceScope").value; descriptor.device.targetScopes = [descriptor.device.defaultScope]; descriptor.device.transport = $("#deviceTransport").value; descriptor.device.targetDevice = descriptor.device.id; renderAll({ preserveResult: true }); } function renderCapabilityEditor() { $("#capabilityEditor").innerHTML = descriptor.capabilities.map((capability, capabilityIndex) => `
${escapeHtml(capability.label)} ${escapeHtml(capability.id)}
${capability.intents.map((intent, intentIndex) => ` `).join("")}
`).join(""); } function renderExamples() { $("#exampleCommands").innerHTML = exampleTemplates.map((makeExample) => { const utterance = makeExample(descriptor.device.name); return ``; }).join(""); } function controlMarkup(capability) { const ui = capability.ui; if (ui.type === "select") { return ` `; } return ` `; } function renderDeviceCard() { const enabledCapabilities = descriptor.capabilities.filter((item) => item.enabled !== false); const category = categoryLabels[descriptor.device.category] || "自定义设备"; const transport = transportLabels[descriptor.device.transport] || descriptor.device.transport; const icon = descriptor.device.category === "seat" ? "◫" : descriptor.device.category === "peripheral" ? "⌁" : "❄"; $("#deviceCardPreview").innerHTML = `

${escapeHtml(descriptor.device.name)}

${escapeHtml(category)} · ${escapeHtml(transport)} · ${escapeHtml(descriptor.device.defaultScope)}

实验就绪
${descriptor.readouts.map((readout) => `
${escapeHtml(readout.label)}${escapeHtml(readout.value)}
`).join("")}
${enabledCapabilities.length ? enabledCapabilities.map(controlMarkup).join("") : "

没有已启用功能。

"}

功能控件只生成结构化演示数据;执行映射为虚拟引用,不连接现有服务。

`; } function compactDescriptor() { return { version: descriptor.version, device: descriptor.device, capabilities: descriptor.capabilities.map((capability) => ({ id: capability.id, property: capability.property, target_scopes: descriptor.device.targetScopes, parameters: capability.parameters, intents: capability.intents, risk_level: capability.risk, confirmation_policy: capability.confirmationPolicy, executor: capability.executor, enabled: capability.enabled, })), }; } function renderDescriptor() { $("#descriptorPreview").textContent = JSON.stringify(compactDescriptor(), null, 2); } function renderAll({ preserveResult = false } = {}) { renderCapabilityEditor(); renderExamples(); renderDeviceCard(); renderDescriptor(); if (!preserveResult) renderResult(null); } function resultPresentation(result) { if (!result) return { status: "idle", title: "等待解析", message: "输入一句话,查看设备、功能和参数的匹配过程。" }; if (result.source === "card_control") { return { status: "success", title: "已生成卡片控制载荷", message: `已生成 ${result.commands.length} 条结构化演示指令;实验页面不会发送到车辆。`, }; } if (result.semantic_status === "recognized_executable") { return { status: "success", title: "已匹配可执行功能", message: `${result.clean_text} 当前仅生成演示载荷。` }; } if (result.semantic_status === "ambiguous" || result.semantic_status === "incomplete") { return { status: "warning", title: "需要补充信息", message: result.issues.join(";") }; } return { status: "error", title: "未生成控制指令", message: result.issues.join(";") || "没有匹配到设备能力。" }; } function renderResult(result) { const presentation = resultPresentation(result); $("#resultBanner").dataset.status = presentation.status; $("#resultTitle").textContent = presentation.title; $("#resultMessage").textContent = presentation.message; $("#traceList").innerHTML = result?.trace?.length ? result.trace.map((item) => `
  • ${escapeHtml(item.stage)}${escapeHtml(item.detail)}
  • `).join("") : "
  • 尚无轨迹解析后将在这里显示每一步决策。
  • "; latestPayload = result; $("#payloadOutput").textContent = JSON.stringify(result || {}, null, 2); $("#copyPayload").disabled = !result; } function parseCurrentUtterance() { renderResult(parseInstruction($("#utterance").value, descriptor)); } function bindEvents() { ["deviceName", "deviceId", "deviceDomain", "deviceCategory", "deviceAliases", "deviceScope", "deviceTransport"] .forEach((id) => $("#" + id).addEventListener("input", syncIdentityFromForm)); $("#capabilityEditor").addEventListener("input", (event) => { const section = event.target.closest("[data-capability-index]"); if (!section) return; const capability = descriptor.capabilities[Number(section.dataset.capabilityIndex)]; if (event.target.matches("[data-capability-enabled]")) { capability.enabled = event.target.checked; } else if (event.target.matches("[data-intent-index]")) { capability.intents[Number(event.target.dataset.intentIndex)].phrases = splitAliases(event.target.value); } renderDeviceCard(); renderDescriptor(); }); $("#exampleCommands").addEventListener("click", (event) => { const button = event.target.closest("[data-example]"); if (!button) return; $("#utterance").value = button.dataset.example; parseCurrentUtterance(); }); $("#parseCommand").addEventListener("click", parseCurrentUtterance); $("#utterance").addEventListener("keydown", (event) => { if ((event.ctrlKey || event.metaKey) && event.key === "Enter") parseCurrentUtterance(); }); $("#deviceCardPreview").addEventListener("input", (event) => { if (!event.target.matches("[data-manual-capability]")) return; const output = event.target.closest(".card-control")?.querySelector("output"); if (output) output.textContent = `${event.target.value}${event.target.dataset.unit || ""}`; const values = Object.fromEntries($$("[data-manual-capability]", $("#deviceCardPreview")) .map((input) => [input.dataset.manualCapability, input.value])); const result = { source: "card_control", semantic_status: "recognized_executable", execution_eligible: false, note: "实验页面不允许真实执行", commands: buildManualCommand(descriptor, values), }; renderResult(result); }); $("#copyPayload").addEventListener("click", async () => { if (!latestPayload) return; await navigator.clipboard.writeText(JSON.stringify(latestPayload, null, 2)); const button = $("#copyPayload"); button.textContent = "已复制"; window.setTimeout(() => { button.textContent = "复制"; }, 1200); }); $("#resetDemo").addEventListener("click", () => { descriptor = cloneDescriptor(DEFAULT_DESCRIPTOR); fillIdentityForm(); $("#utterance").value = "把冰箱温度调到4度"; renderAll(); parseCurrentUtterance(); }); } fillIdentityForm(); renderAll(); bindEvents(); parseCurrentUtterance();