fix: compose named cockpit modes from capabilities
This commit is contained in:
+56
-7
@@ -8310,20 +8310,69 @@ window.CognitiveAgentConsole = {
|
||||
? draft.trim()
|
||||
: String(draft?.instruction || "").trim();
|
||||
if (!instruction) throw new Error("功能模式草稿缺少可试运行的指令。");
|
||||
const { payload } = await requestNaturalLanguageControl(instruction, {
|
||||
executionMode: "dry_run",
|
||||
clientType: "assistant_mode_trial",
|
||||
provider: "local",
|
||||
previewOnly: false,
|
||||
});
|
||||
let payload;
|
||||
if (draft && typeof draft === "object" && draft.source_request_id) {
|
||||
const response = await apiRequest("/api/mode-profile-drafts/trial", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
name: draft.name,
|
||||
instruction,
|
||||
source_request_id: draft.source_request_id,
|
||||
vehicle_id: draft.vehicle_id || state.vehicleId || null,
|
||||
}),
|
||||
timeoutMs: 30 * 1000,
|
||||
retries: 0,
|
||||
});
|
||||
payload = await response.json();
|
||||
if (response.status === 401) {
|
||||
window.location.replace("/login");
|
||||
return null;
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(payload.message || payload.error || `HTTP ${response.status}`);
|
||||
}
|
||||
} else {
|
||||
({ payload } = await requestNaturalLanguageControl(instruction, {
|
||||
executionMode: "dry_run",
|
||||
clientType: "assistant_mode_trial",
|
||||
provider: "local",
|
||||
previewOnly: false,
|
||||
}));
|
||||
}
|
||||
if (!payload) throw new Error("模拟试运行没有返回结果。");
|
||||
const actualOperationCount = Object.values(payload.function_configs || {})
|
||||
.reduce((total, config) => total + (config?.spec?.operations?.length || 0), 0);
|
||||
const expectedOperationCount = Number(
|
||||
typeof draft === "object" ? draft?.operation_count : 0,
|
||||
);
|
||||
const actionSignature = (action) => {
|
||||
const target = action?.target && typeof action.target === "object"
|
||||
? action.target.instance || action.target.zone || action.target.device || ""
|
||||
: action?.target || "";
|
||||
const parameters = action?.parameters && typeof action.parameters === "object"
|
||||
? Object.fromEntries(Object.entries(action.parameters).sort(([left], [right]) => left.localeCompare(right)))
|
||||
: {};
|
||||
return JSON.stringify([
|
||||
String(action?.domain || ""),
|
||||
String(action?.capability || ""),
|
||||
String(target),
|
||||
parameters,
|
||||
]);
|
||||
};
|
||||
const expectedSignatures = Array.isArray(draft?.actions)
|
||||
? draft.actions.map(actionSignature).sort()
|
||||
: [];
|
||||
const actualActions = Array.isArray(payload?.intent_ir?.spec?.actions)
|
||||
? payload.intent_ir.spec.actions
|
||||
: [];
|
||||
const actualSignatures = actualActions.map(actionSignature).sort();
|
||||
const sameActions = expectedSignatures.length === 0
|
||||
|| (expectedSignatures.length === actualSignatures.length
|
||||
&& expectedSignatures.every((signature, index) => signature === actualSignatures[index]));
|
||||
if (payload.status !== "succeeded"
|
||||
|| (expectedOperationCount > 0 && actualOperationCount < expectedOperationCount)) {
|
||||
|| (expectedOperationCount > 0 && actualOperationCount < expectedOperationCount)
|
||||
|| !sameActions) {
|
||||
throw new Error("试运行未完整覆盖草稿动作,请重新生成模式预览。");
|
||||
}
|
||||
return payload;
|
||||
|
||||
@@ -271,7 +271,7 @@
|
||||
followLatest();
|
||||
}
|
||||
|
||||
function appendModeCreatedState(message, profile) {
|
||||
function appendModeCreatedState(message, profile, { updated = false } = {}) {
|
||||
if (!message?.body || !profile?.id) return;
|
||||
const selector = `[data-assistant-mode-created="${CSS.escape(String(profile.id))}"]`;
|
||||
if ($(selector, message.body)) return;
|
||||
@@ -286,7 +286,7 @@
|
||||
const copy = document.createElement("span");
|
||||
copy.className = "assistant-mode-created-copy";
|
||||
const label = document.createElement("small");
|
||||
label.textContent = "已添加到「我的模式」";
|
||||
label.textContent = updated ? "已更新「我的模式」" : "已添加到「我的模式」";
|
||||
const name = document.createElement("strong");
|
||||
name.textContent = String(profile.name || "新功能模式");
|
||||
copy.append(label, name);
|
||||
@@ -396,9 +396,16 @@
|
||||
icon = "panels-top-left";
|
||||
order = instructionOrder("车窗");
|
||||
} else if (domain === "seat") {
|
||||
label = ["passenger", "front_right"].includes(zone) ? "副驾座椅" : "主驾座椅";
|
||||
const seatLabel = ["passenger", "front_right"].includes(zone) ? "副驾" : "主驾";
|
||||
const seatCapabilityLabels = {
|
||||
"seat.set_longitudinal_position": "座椅前后位置",
|
||||
"seat.set_cushion_tilt": "坐垫角度",
|
||||
"seat.set_backrest_recline": "靠背角度",
|
||||
"seat.set_legrest_height": "腿托高度",
|
||||
};
|
||||
label = `${seatLabel}${seatCapabilityLabels[capability] || "座椅"}`;
|
||||
value = params.position_pct != null
|
||||
? `${params.position_pct}%`
|
||||
? `${params.position_pct}%${params.angle_deg != null ? `(${params.angle_deg}°)` : ""}`
|
||||
: motionLabel(params.state || params.mode_id);
|
||||
icon = "armchair";
|
||||
order = instructionOrder("座椅", "副驾", "主驾");
|
||||
@@ -408,8 +415,12 @@
|
||||
icon = "thermometer";
|
||||
order = instructionOrder("空调", "温度");
|
||||
} else if (domain === "climate") {
|
||||
label = "空调";
|
||||
value = params.level != null ? `${params.level}档` : motionLabel(params.state);
|
||||
const cycleLabels = { internal: "内循环", external: "外循环", auto: "自动循环" };
|
||||
label = capability === "climate.set_fan_level" ? "空调风量"
|
||||
: capability === "climate.set_cycle_mode" ? "空调循环" : "空调";
|
||||
value = params.level != null ? `${params.level}档`
|
||||
: params.mode != null ? (cycleLabels[String(params.mode)] || String(params.mode))
|
||||
: motionLabel(params.state);
|
||||
icon = "fan";
|
||||
order = instructionOrder("空调", "风量");
|
||||
} else if (capability === "interior_light.set_reading_power") {
|
||||
@@ -433,9 +444,15 @@
|
||||
card.classList.add("is-busy");
|
||||
status.hidden = false;
|
||||
status.className = "assistant-mode-draft-status working";
|
||||
status.textContent = "正在保存到「我的模式」…";
|
||||
const existingModeId = String(draft.existing_mode_id || "").trim();
|
||||
status.textContent = existingModeId
|
||||
? "正在将验证结果保存为模式更新…"
|
||||
: "正在保存到「我的模式」…";
|
||||
try {
|
||||
const response = await window.CognitiveAgentNetwork.request("/api/mode-profiles", {
|
||||
const endpoint = existingModeId
|
||||
? `/api/mode-profiles/${encodeURIComponent(existingModeId)}`
|
||||
: "/api/mode-profiles";
|
||||
const response = await window.CognitiveAgentNetwork.request(endpoint, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
@@ -456,10 +473,10 @@
|
||||
card.classList.remove("is-busy");
|
||||
card.classList.add("is-saved");
|
||||
status.className = "assistant-mode-draft-status success";
|
||||
status.textContent = "模式已保存";
|
||||
status.textContent = existingModeId ? "模式已更新" : "模式已保存";
|
||||
card.querySelector(".assistant-mode-draft-actions")?.setAttribute("hidden", "");
|
||||
await window.CognitiveAgentConsole?.refreshModeProfiles?.(payload.mode_profile?.id);
|
||||
appendModeCreatedState(message, payload.mode_profile);
|
||||
appendModeCreatedState(message, payload.mode_profile, { updated: Boolean(existingModeId) });
|
||||
persistMessages();
|
||||
} catch (error) {
|
||||
card.classList.remove("is-busy");
|
||||
@@ -469,14 +486,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function tryModeProfileDraft(card, draft, buttons, status) {
|
||||
async function tryModeProfileDraft(card, draft, actions, buttons, status) {
|
||||
buttons.forEach((button) => { button.disabled = true; });
|
||||
card.classList.add("is-busy");
|
||||
status.hidden = false;
|
||||
status.className = "assistant-mode-draft-status working";
|
||||
status.textContent = "正在进行模拟试运行…";
|
||||
try {
|
||||
const result = await window.CognitiveAgentConsole?.tryModeDraft?.(draft);
|
||||
const result = await window.CognitiveAgentConsole?.tryModeDraft?.({ ...draft, actions });
|
||||
if (!result || result.status !== "succeeded") throw new Error("试运行未通过,请检查动作设置。");
|
||||
status.className = "assistant-mode-draft-status success";
|
||||
status.textContent = "试运行通过 · 尚未保存";
|
||||
@@ -501,7 +518,9 @@
|
||||
const heading = document.createElement("header");
|
||||
const headingCopy = document.createElement("span");
|
||||
const eyebrow = document.createElement("small");
|
||||
eyebrow.textContent = "已为你生成功能模式";
|
||||
eyebrow.textContent = draft.existing_mode_id
|
||||
? "已载入现有功能模式"
|
||||
: "已为你生成功能模式";
|
||||
const title = document.createElement("strong");
|
||||
title.textContent = `「${draft.name}」`;
|
||||
headingCopy.append(eyebrow, title);
|
||||
@@ -530,7 +549,9 @@
|
||||
const save = document.createElement("button");
|
||||
save.type = "button";
|
||||
save.className = "assistant-mode-draft-save";
|
||||
save.innerHTML = '<i data-lucide="bookmark-plus"></i><span>保存为功能模式</span>';
|
||||
save.innerHTML = draft.existing_mode_id
|
||||
? '<i data-lucide="refresh-cw"></i><span>确认更新此模式</span>'
|
||||
: '<i data-lucide="bookmark-plus"></i><span>保存为功能模式</span>';
|
||||
const trial = document.createElement("button");
|
||||
trial.type = "button";
|
||||
trial.className = "assistant-mode-draft-trial";
|
||||
@@ -545,7 +566,7 @@
|
||||
void saveModeProfileDraft(message, card, draft, buttons, status);
|
||||
});
|
||||
trial.addEventListener("click", () => {
|
||||
void tryModeProfileDraft(card, draft, buttons, status);
|
||||
void tryModeProfileDraft(card, draft, actions, buttons, status);
|
||||
});
|
||||
|
||||
card.append(heading, list, controls, status);
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
<script src="/vendor/three/0.147.0/fflate.min.js" defer></script>
|
||||
<script src="/vendor/three/0.147.0/FBXLoader.js" defer></script>
|
||||
<script src="/network.js?v=android-pad-2" defer></script>
|
||||
<script src="/app.js?v=dual-seat-126" defer></script>
|
||||
<script src="/app.js?v=dual-seat-127" defer></script>
|
||||
<script src="/speech-input.js?v=sensevoice-12" defer></script>
|
||||
<script type="module" src="/gesture-control.js?v=android-pad-9"></script>
|
||||
<script src="/pwa.js?v=android-pad-3" defer></script>
|
||||
<script src="/console-v2.js?v=delivery-state-62" defer></script>
|
||||
<script src="/self-extension.js?v=gscu-tcp-43" defer></script>
|
||||
<script src="/assistant-chat.js?v=codex-assistant-20" defer></script>
|
||||
<script src="/assistant-chat.js?v=codex-assistant-22" defer></script>
|
||||
<script src="/energy-monitor.js?v=energy-monitor-4" defer></script>
|
||||
</head>
|
||||
<body class="console-v2">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const CACHE_VERSION = "cognitive-agent-pad-v269";
|
||||
const CACHE_VERSION = "cognitive-agent-pad-v271";
|
||||
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
|
||||
const RUNTIME_CACHE = `${CACHE_VERSION}-runtime`;
|
||||
const APP_SHELL = [
|
||||
@@ -6,7 +6,7 @@ const APP_SHELL = [
|
||||
"/index.html",
|
||||
"/styles.css?v=dual-seat-23",
|
||||
"/network.js?v=android-pad-2",
|
||||
"/app.js?v=dual-seat-126",
|
||||
"/app.js?v=dual-seat-127",
|
||||
"/gesture-control.js?v=android-pad-9",
|
||||
"/pwa.js?v=android-pad-3",
|
||||
"/console-v2.css?v=delivery-state-65",
|
||||
@@ -17,7 +17,7 @@ const APP_SHELL = [
|
||||
"/speech-input.js?v=sensevoice-12",
|
||||
"/speech-audio-worklet.js?v=sensevoice-5",
|
||||
"/assistant-chat.css?v=codex-assistant-22",
|
||||
"/assistant-chat.js?v=codex-assistant-20",
|
||||
"/assistant-chat.js?v=codex-assistant-22",
|
||||
"/energy-monitor.css?v=energy-monitor-4",
|
||||
"/energy-monitor.js?v=energy-monitor-4",
|
||||
"/assets/pets/self-extension-cat.png?v=3",
|
||||
|
||||
Reference in New Issue
Block a user