fix: support conversational mode draft updates

This commit is contained in:
vee
2026-09-07 23:59:42 +08:00
parent 6b2b644ee6
commit f0a248ede3
19 changed files with 2326 additions and 109 deletions
+14
View File
@@ -634,6 +634,15 @@
background: #fff0f2;
}
.assistant-mode-draft-status.superseded {
color: #687386;
background: #f1f3f6;
}
.assistant-mode-draft.is-superseded {
opacity: 0.72;
}
.assistant-mode-draft.is-saved {
border-color: #bfe3d4;
}
@@ -1191,6 +1200,11 @@
background: var(--status-error-soft);
}
.assistant-mode-draft-status.superseded {
color: var(--assistant-muted);
background: var(--muted);
}
.assistant-mode-draft.is-saved {
border-color: color-mix(in oklch, var(--status-success) 36%, var(--border));
}
+139
View File
@@ -1,6 +1,7 @@
(function initializeUnifiedAssistant() {
const STORAGE_KEY = "cognitive-agent.assistant-messages";
const CONVERSATION_STORAGE_KEY = "cognitive-agent.conversation-id";
const ACTIVE_MODE_DRAFT_STORAGE_KEY = "cognitive-agent.active-mode-draft";
const MAX_STORED_MESSAGES = 24;
const COMPOSER_MIN_HEIGHT = 64;
const COMPOSER_MAX_HEIGHT = 116;
@@ -8,6 +9,7 @@
let pendingController = null;
let pendingMessage = null;
let latestControlResult = null;
let activeModeDraftContext = null;
let initialized = false;
const $ = (selector, root = document) => root.querySelector(selector);
@@ -106,6 +108,108 @@
return created;
}
function normalizeModeName(value) {
return String(value || "").trim().replace(/\s+/g, "").toLowerCase();
}
function sanitizeModeDraftContext(value) {
if (!value || typeof value !== "object") return null;
const name = String(value.name || "").trim();
const sourceRequestId = String(value.source_request_id || "").trim();
const vehicleId = String(value.vehicle_id || "").trim();
const draftConversationId = String(value.conversation_id || "").trim();
if (
!name
|| name.length > 64
|| !/^req-[a-z0-9-]{6,100}$/.test(sourceRequestId)
) return null;
if (!vehicleId || vehicleId.length > 80 || draftConversationId !== conversationId()) return null;
return {
name,
source_request_id: sourceRequestId,
vehicle_id: vehicleId,
conversation_id: draftConversationId,
};
}
function loadActiveModeDraftContext() {
try {
return sanitizeModeDraftContext(
JSON.parse(sessionStorage.getItem(ACTIVE_MODE_DRAFT_STORAGE_KEY) || "null"),
);
} catch (_error) {
return null;
}
}
function rememberModeDraftContext(draft) {
const candidate = sanitizeModeDraftContext({
name: draft?.name,
source_request_id: draft?.source_request_id,
vehicle_id: draft?.vehicle_id || $("#vehicleSelect")?.value || null,
conversation_id: conversationId(),
});
if (!candidate) return;
activeModeDraftContext = candidate;
sessionStorage.setItem(ACTIVE_MODE_DRAFT_STORAGE_KEY, JSON.stringify(candidate));
}
function clearActiveModeDraftContext(sourceRequestId = null) {
if (
sourceRequestId
&& activeModeDraftContext?.source_request_id !== String(sourceRequestId)
) return;
activeModeDraftContext = null;
sessionStorage.removeItem(ACTIVE_MODE_DRAFT_STORAGE_KEY);
}
function isModeDraftUpdateRequest(message, context) {
const compact = String(message || "").trim().replace(/\s+/g, "");
if (!compact || !context?.name) return false;
const politePrefix = "(?:(?:请|麻烦)(?:帮我|帮忙|给我)?|帮我|帮忙|给我)?";
const prefixUpdateVerb = "(?:修改一下|修改|调整|更改|更新|改一下|变更)";
const changePredicate = "(?:修改|改成|改为|更改|更新|变更|变为|换成|调整|调节|调高|调低|调到|调成|调为|调至|设置|设为|设成|设到|设至|打开|开启|启动|关闭|关掉|关上|全关|停止|停用|切换|进入|退出|移动|前移|后退|前进|升高|降低|抬高|放低|降下|升起|调直|放倒|复位|半开|开到|关到|拉开|拉上|上锁|解锁)";
const modeName = "([^,。!?!?、;;::]{1,64}?模式)";
const modePossessiveBoundary = "(?:中的|里的|内的|之中的|的|中|里|内)";
const modeMemberBoundary = `(?:${modePossessiveBoundary}|[,,::])`;
const question = /为什么|什么|怎么|如何|介绍|解释|作用|区别|原理|哪些|多少|几(?:度|档|级|个|项)?|是否|能否|可不可以|合适吗|合理吗|会怎样|列出|告诉我|[??]|吗[。.!!]?$/;
const candidates = [
{
pattern: new RegExp(`^${politePrefix}${prefixUpdateVerb}${modeName}${modeMemberBoundary}[,,::]?(.*)$`),
predicateRequired: false,
},
{
pattern: new RegExp(`^${politePrefix}(?:把|将)${modeName}${modeMemberBoundary}[,,::]?(.+)$`),
predicateRequired: true,
},
{
pattern: new RegExp(`^${politePrefix}${modeName}${modeMemberBoundary}[,,::]?(.+)$`),
predicateRequired: true,
},
];
return candidates.some(({ pattern, predicateRequired }) => {
const matched = compact.match(pattern);
const change = String(matched?.[2] || "");
return Boolean(
matched
&& normalizeModeName(matched[1]) === normalizeModeName(context.name)
&& !question.test(change)
&& (!predicateRequired || new RegExp(changePredicate).test(change))
);
});
}
function requestModeDraftContext(message) {
const context = activeModeDraftContext || loadActiveModeDraftContext();
if (!context || !isModeDraftUpdateRequest(message, context)) return null;
activeModeDraftContext = context;
return {
name: context.name,
source_request_id: context.source_request_id,
vehicle_id: context.vehicle_id,
};
}
function loadStoredMessages() {
try {
const stored = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || "[]");
@@ -475,6 +579,7 @@
status.className = "assistant-mode-draft-status success";
status.textContent = existingModeId ? "模式已更新" : "模式已保存";
card.querySelector(".assistant-mode-draft-actions")?.setAttribute("hidden", "");
clearActiveModeDraftContext(draft.source_request_id);
await window.CognitiveAgentConsole?.refreshModeProfiles?.(payload.mode_profile?.id);
appendModeCreatedState(message, payload.mode_profile, { updated: Boolean(existingModeId) });
persistMessages();
@@ -506,13 +611,40 @@
}
}
function supersedeModeDraftCards(draft) {
const nextName = normalizeModeName(draft?.name);
const nextRequestId = String(draft?.source_request_id || "");
if (!nextName || !nextRequestId) return;
document.querySelectorAll("[data-assistant-mode-draft]").forEach((card) => {
if (
card.dataset.assistantModeName !== nextName
|| card.dataset.assistantModeDraft === nextRequestId
) return;
card.classList.remove("is-busy");
card.classList.add("is-superseded");
card.querySelectorAll("button").forEach((button) => {
button.disabled = true;
});
card.querySelector(".assistant-mode-draft-actions")?.setAttribute("hidden", "");
const priorStatus = card.querySelector(".assistant-mode-draft-status");
if (priorStatus) {
priorStatus.hidden = false;
priorStatus.className = "assistant-mode-draft-status superseded";
priorStatus.textContent = "已由下方最新草稿替换";
}
});
}
function appendModeDraftState(message, draft, actions) {
if (!message?.body || !draft?.name || !draft?.source_request_id) return;
rememberModeDraftContext(draft);
supersedeModeDraftCards(draft);
const selector = `[data-assistant-mode-draft="${CSS.escape(String(draft.source_request_id))}"]`;
if ($(selector, message.body)) return;
const card = document.createElement("section");
card.className = "assistant-mode-draft";
card.dataset.assistantModeDraft = String(draft.source_request_id);
card.dataset.assistantModeName = normalizeModeName(draft.name);
message.article?.classList.add("assistant-message-mode-draft");
const heading = document.createElement("header");
@@ -613,6 +745,7 @@
const controlTransport = localStorage.getItem("cognitive-agent.control-transport") || "wired_can";
const hardwareClientId = localStorage.getItem("cognitive-agent.hardware-client-id");
const vehicleId = $("#vehicleSelect")?.value || null;
const modeProfileDraftContext = requestModeDraftContext(message);
return {
message,
conversation_id: conversationId(),
@@ -620,6 +753,9 @@
execution_mode: executionMode,
control_transport: controlTransport,
...(vehicleId ? { vehicle_id: vehicleId } : {}),
...(modeProfileDraftContext
? { mode_profile_draft_context: modeProfileDraftContext }
: {}),
...(executionMode === "hardware" && controlTransport !== "wireless_mqtt" && hardwareClientId
? { hardware_client_id: hardwareClientId }
: {}),
@@ -681,6 +817,7 @@
event.payload.mode_profile
&& event.payload.orchestration_route === "mode_profile_create"
) {
clearActiveModeDraftContext();
appendModeCreatedState(message, event.payload.mode_profile);
}
}
@@ -859,6 +996,7 @@
resizeComposerInput(input);
}, 300);
renderInitialMessages();
activeModeDraftContext = loadActiveModeDraftContext();
setComposerPending(false);
}
@@ -873,6 +1011,7 @@
const target = event.target instanceof Element ? event.target : null;
if (!target?.closest("#newConversation")) return;
sessionStorage.removeItem(STORAGE_KEY);
clearActiveModeDraftContext();
sessionStorage.setItem(CONVERSATION_STORAGE_KEY, createConversationId());
}, true);
+2 -2
View File
@@ -13,7 +13,7 @@
<link rel="stylesheet" href="/console-v2.css?v=delivery-state-65" />
<link rel="stylesheet" href="/self-extension.css?v=gscu-tcp-43" />
<link rel="stylesheet" href="/speech-input.css?v=sensevoice-7" />
<link rel="stylesheet" href="/assistant-chat.css?v=codex-assistant-22" />
<link rel="stylesheet" href="/assistant-chat.css?v=codex-assistant-23" />
<link rel="stylesheet" href="/energy-monitor.css?v=energy-monitor-4" />
<script src="/vendor/lucide/0.468.0/lucide.min.js" defer></script>
<script src="/vendor/three/0.147.0/three.min.js" defer></script>
@@ -26,7 +26,7 @@
<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-22" defer></script>
<script src="/assistant-chat.js?v=codex-assistant-23" defer></script>
<script src="/energy-monitor.js?v=energy-monitor-4" defer></script>
</head>
<body class="console-v2">
+3 -3
View File
@@ -1,4 +1,4 @@
const CACHE_VERSION = "cognitive-agent-pad-v271";
const CACHE_VERSION = "cognitive-agent-pad-v272";
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
const RUNTIME_CACHE = `${CACHE_VERSION}-runtime`;
const APP_SHELL = [
@@ -16,8 +16,8 @@ const APP_SHELL = [
"/speech-input.css?v=sensevoice-7",
"/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-22",
"/assistant-chat.css?v=codex-assistant-23",
"/assistant-chat.js?v=codex-assistant-23",
"/energy-monitor.css?v=energy-monitor-4",
"/energy-monitor.js?v=energy-monitor-4",
"/assets/pets/self-extension-cat.png?v=3",