feat(web-console): unify spatial UI and chat reset
This commit is contained in:
@@ -8854,10 +8854,9 @@ function downloadArtifact(type) {
|
|||||||
URL.revokeObjectURL(link.href);
|
URL.revokeObjectURL(link.href);
|
||||||
}
|
}
|
||||||
|
|
||||||
function startNewConversation() {
|
function startNewConversation(event) {
|
||||||
state.conversationId = createConversationId();
|
state.conversationId = String(event?.detail?.conversationId || createConversationId());
|
||||||
sessionStorage.setItem("cognitive-agent.conversation-id", state.conversationId);
|
sessionStorage.setItem("cognitive-agent.conversation-id", state.conversationId);
|
||||||
window.location.reload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.CognitiveAgentConsole = {
|
window.CognitiveAgentConsole = {
|
||||||
@@ -9024,7 +9023,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
$("#requestForm").requestSubmit();
|
$("#requestForm").requestSubmit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$("#newConversation").addEventListener("click", startNewConversation);
|
window.addEventListener("cognitive-agent:new-conversation", startNewConversation);
|
||||||
$("#vehicleSelect").addEventListener("change", (event) => {
|
$("#vehicleSelect").addEventListener("change", (event) => {
|
||||||
selectVehicle(event.target.value);
|
selectVehicle(event.target.value);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1512,21 +1512,42 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 30px minmax(0, 1fr);
|
||||||
|
column-gap: 9px;
|
||||||
|
row-gap: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assistant-message-with-dispatched-controls > .assistant-avatar {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.assistant-message-with-dispatched-controls .assistant-message-body {
|
.assistant-message-with-dispatched-controls .assistant-message-body {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
flex: 1 1 0;
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assistant-message-with-dispatched-controls .assistant-message-body > :not(
|
||||||
|
.assistant-dispatched-controls-vue-host,
|
||||||
|
.assistant-dispatched-controls
|
||||||
|
) {
|
||||||
|
grid-column: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.assistant-dispatched-controls-vue-host {
|
.assistant-dispatched-controls-vue-host {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 336px;
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assistant-message-with-dispatched-controls .assistant-dispatched-controls-vue-host,
|
||||||
|
.assistant-message-with-dispatched-controls .assistant-message-body > .assistant-dispatched-controls {
|
||||||
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.assistant-dispatched-controls {
|
.assistant-dispatched-controls {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 336px;
|
max-width: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: var(--assistant-text, #233044);
|
color: var(--assistant-text, #233044);
|
||||||
|
|||||||
@@ -72,7 +72,14 @@
|
|||||||
trigger.setAttribute("aria-label", "展开AI助手");
|
trigger.setAttribute("aria-label", "展开AI助手");
|
||||||
trigger.title = "展开AI助手";
|
trigger.title = "展开AI助手";
|
||||||
trigger.innerHTML = '<i data-lucide="bot-message-square"></i><span class="visually-hidden">AI助手</span><b aria-hidden="true"></b>';
|
trigger.innerHTML = '<i data-lucide="bot-message-square"></i><span class="visually-hidden">AI助手</span><b aria-hidden="true"></b>';
|
||||||
($("#v2PanelControlDock") || document.body).append(trigger);
|
const panelDock = $("#v2PanelControlDock");
|
||||||
|
if (panelDock) {
|
||||||
|
const firstRightPanelToggle = $("[data-panel-toggle]", panelDock);
|
||||||
|
if (firstRightPanelToggle) panelDock.insertBefore(trigger, firstRightPanelToggle);
|
||||||
|
else panelDock.append(trigger);
|
||||||
|
} else {
|
||||||
|
document.body.append(trigger);
|
||||||
|
}
|
||||||
|
|
||||||
trigger.addEventListener("click", () => {
|
trigger.addEventListener("click", () => {
|
||||||
setDrawerOpen(!document.body.classList.contains("assistant-drawer-open"));
|
setDrawerOpen(!document.body.classList.contains("assistant-drawer-open"));
|
||||||
@@ -1405,17 +1412,22 @@
|
|||||||
const assistantMessage = createMessage("assistant", "", { pending: true });
|
const assistantMessage = createMessage("assistant", "", { pending: true });
|
||||||
pendingMessage = assistantMessage;
|
pendingMessage = assistantMessage;
|
||||||
latestControlResult = null;
|
latestControlResult = null;
|
||||||
pendingController = new AbortController();
|
const requestController = new AbortController();
|
||||||
|
pendingController = requestController;
|
||||||
setComposerPending(true);
|
setComposerPending(true);
|
||||||
|
|
||||||
let completedError = null;
|
let completedError = null;
|
||||||
|
let resetForNewConversation = false;
|
||||||
try {
|
try {
|
||||||
await streamAssistant(messageText, assistantMessage, pendingController.signal);
|
await streamAssistant(messageText, assistantMessage, requestController.signal);
|
||||||
setMessageContent(assistantMessage, assistantMessage.content, { final: true });
|
setMessageContent(assistantMessage, assistantMessage.content, { final: true });
|
||||||
persistMessages();
|
persistMessages();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
completedError = error;
|
completedError = error;
|
||||||
if (pendingController.signal.aborted) {
|
resetForNewConversation = requestController.signal.aborted
|
||||||
|
&& requestController.signal.reason === "new_conversation";
|
||||||
|
if (resetForNewConversation) return;
|
||||||
|
if (requestController.signal.aborted) {
|
||||||
const stopped = assistantMessage.content
|
const stopped = assistantMessage.content
|
||||||
? `${assistantMessage.content}\n\n已停止继续生成。已下发的车辆操作仍会由安全执行链路完成。`
|
? `${assistantMessage.content}\n\n已停止继续生成。已下发的车辆操作仍会由安全执行链路完成。`
|
||||||
: "已停止生成。已下发的车辆操作仍会由安全执行链路完成。";
|
: "已停止生成。已下发的车辆操作仍会由安全执行链路完成。";
|
||||||
@@ -1429,17 +1441,21 @@
|
|||||||
}
|
}
|
||||||
persistMessages();
|
persistMessages();
|
||||||
} finally {
|
} finally {
|
||||||
pendingController = null;
|
if (pendingController === requestController) {
|
||||||
pendingMessage = null;
|
pendingController = null;
|
||||||
setComposerPending(false);
|
pendingMessage = null;
|
||||||
input.focus({ preventScroll: true });
|
setComposerPending(false);
|
||||||
window.dispatchEvent(new CustomEvent("cognitive-agent:request-complete", {
|
}
|
||||||
detail: {
|
if (!resetForNewConversation) {
|
||||||
utterance: messageText,
|
input.focus({ preventScroll: true });
|
||||||
result: latestControlResult,
|
window.dispatchEvent(new CustomEvent("cognitive-agent:request-complete", {
|
||||||
error: completedError,
|
detail: {
|
||||||
},
|
utterance: messageText,
|
||||||
}));
|
result: latestControlResult,
|
||||||
|
error: completedError,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1490,6 +1506,37 @@
|
|||||||
stored.forEach((message) => createMessage(message.role, String(message.content || "")));
|
stored.forEach((message) => createMessage(message.role, String(message.content || "")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetConversation() {
|
||||||
|
const activeController = pendingController;
|
||||||
|
pendingController = null;
|
||||||
|
pendingMessage = null;
|
||||||
|
latestControlResult = null;
|
||||||
|
if (activeController) activeController.abort("new_conversation");
|
||||||
|
|
||||||
|
sessionStorage.removeItem(STORAGE_KEY);
|
||||||
|
clearActiveModeDraftContext();
|
||||||
|
const nextConversationId = createConversationId();
|
||||||
|
sessionStorage.setItem(CONVERSATION_STORAGE_KEY, nextConversationId);
|
||||||
|
|
||||||
|
const input = $("#utterance");
|
||||||
|
if (input) {
|
||||||
|
input.value = "";
|
||||||
|
delete input.dataset.speechStage;
|
||||||
|
delete input.dataset.speechUtteranceId;
|
||||||
|
delete input.dataset.speechValue;
|
||||||
|
input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
resizeComposerInput(input);
|
||||||
|
}
|
||||||
|
renderInitialMessages();
|
||||||
|
setComposerPending(false);
|
||||||
|
setDrawerOpen(true);
|
||||||
|
requestAnimationFrame(() => input?.focus({ preventScroll: true }));
|
||||||
|
window.dispatchEvent(new CustomEvent("cognitive-agent:new-conversation", {
|
||||||
|
detail: { conversationId: nextConversationId },
|
||||||
|
}));
|
||||||
|
return nextConversationId;
|
||||||
|
}
|
||||||
|
|
||||||
function initialize() {
|
function initialize() {
|
||||||
if (initialized || !$("#requestForm") || !$("#assistantMessages")) return;
|
if (initialized || !$("#requestForm") || !$("#assistantMessages")) return;
|
||||||
initialized = true;
|
initialized = true;
|
||||||
@@ -1526,9 +1573,8 @@
|
|||||||
document.addEventListener("click", (event) => {
|
document.addEventListener("click", (event) => {
|
||||||
const target = event.target instanceof Element ? event.target : null;
|
const target = event.target instanceof Element ? event.target : null;
|
||||||
if (!target?.closest("#newConversation")) return;
|
if (!target?.closest("#newConversation")) return;
|
||||||
sessionStorage.removeItem(STORAGE_KEY);
|
event.preventDefault?.();
|
||||||
clearActiveModeDraftContext();
|
resetConversation();
|
||||||
sessionStorage.setItem(CONVERSATION_STORAGE_KEY, createConversationId());
|
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", initialize);
|
document.addEventListener("DOMContentLoaded", initialize);
|
||||||
@@ -1536,6 +1582,7 @@
|
|||||||
submit: submitAssistant,
|
submit: submitAssistant,
|
||||||
open: () => setDrawerOpen(true),
|
open: () => setDrawerOpen(true),
|
||||||
close: () => setDrawerOpen(false),
|
close: () => setDrawerOpen(false),
|
||||||
|
resetConversation,
|
||||||
startModeCreation,
|
startModeCreation,
|
||||||
announceModeExecution,
|
announceModeExecution,
|
||||||
announceModeCreated(profile) {
|
announceModeCreated(profile) {
|
||||||
|
|||||||
@@ -35,11 +35,11 @@
|
|||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="/styles.css?v=dual-seat-23" />
|
<link rel="stylesheet" href="/styles.css?v=dual-seat-23" />
|
||||||
<link rel="stylesheet" href="/console-v2.css?v=delivery-state-69" />
|
<link rel="stylesheet" href="/console-v2.css?v=delivery-state-69" />
|
||||||
<link rel="stylesheet" href="/self-extension.css?v=pet-settings-49" />
|
<link rel="stylesheet" href="/self-extension.css?v=pet-settings-52" />
|
||||||
<link rel="stylesheet" href="/speech-input.css?v=sensevoice-7" />
|
<link rel="stylesheet" href="/speech-input.css?v=sensevoice-7" />
|
||||||
<link rel="stylesheet" href="/assistant-chat.css?v=codex-assistant-28" />
|
<link rel="stylesheet" href="/assistant-chat.css?v=codex-assistant-29" />
|
||||||
<link rel="stylesheet" href="/energy-monitor.css?v=energy-monitor-4" />
|
<link rel="stylesheet" href="/energy-monitor.css?v=energy-monitor-4" />
|
||||||
<link rel="stylesheet" href="/ui-consistency.css?v=spatial-ui-4" />
|
<link rel="stylesheet" href="/ui-consistency.css?v=spatial-ui-5" />
|
||||||
<link rel="stylesheet" href="/codepen-glass-theme.css?v=creative-cloud-2" />
|
<link rel="stylesheet" href="/codepen-glass-theme.css?v=creative-cloud-2" />
|
||||||
<script src="/vendor/lucide/0.468.0/lucide.min.js" defer></script>
|
<script src="/vendor/lucide/0.468.0/lucide.min.js" defer></script>
|
||||||
<script src="/vendor/vue/3.5.21/vue.runtime.global.prod.js" defer></script>
|
<script src="/vendor/vue/3.5.21/vue.runtime.global.prod.js" defer></script>
|
||||||
@@ -48,13 +48,13 @@
|
|||||||
<script src="/vendor/three/0.147.0/fflate.min.js" defer></script>
|
<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="/vendor/three/0.147.0/FBXLoader.js" defer></script>
|
||||||
<script src="/network.js?v=android-pad-2" defer></script>
|
<script src="/network.js?v=android-pad-2" defer></script>
|
||||||
<script src="/app.js?v=dual-seat-139" defer></script>
|
<script src="/app.js?v=dual-seat-140" defer></script>
|
||||||
<script src="/speech-input.js?v=sensevoice-13" defer></script>
|
<script src="/speech-input.js?v=sensevoice-13" defer></script>
|
||||||
<script type="module" src="/gesture-control.js?v=android-pad-9"></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="/pwa.js?v=android-pad-3" defer></script>
|
||||||
<script src="/console-v2.js?v=local-first-70" defer></script>
|
<script src="/console-v2.js?v=local-first-70" defer></script>
|
||||||
<script src="/self-extension.js?v=pet-settings-49" defer></script>
|
<script src="/self-extension.js?v=pet-settings-52" defer></script>
|
||||||
<script src="/assistant-chat.js?v=codex-assistant-32" defer></script>
|
<script src="/assistant-chat.js?v=codex-assistant-33" defer></script>
|
||||||
<script src="/energy-monitor.js?v=energy-monitor-4" defer></script>
|
<script src="/energy-monitor.js?v=energy-monitor-4" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="console-v2">
|
<body class="console-v2">
|
||||||
|
|||||||
@@ -2351,10 +2351,11 @@ form > .v2-extension-primary {
|
|||||||
padding: 15px 20px;
|
padding: 15px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-gscu-config-form > label,
|
.v2-extension-dialog .v2-gscu-config-form > label,
|
||||||
.v2-gscu-actions label {
|
.v2-extension-dialog .v2-gscu-actions label {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
|
margin: 0;
|
||||||
color: #4c6263;
|
color: #4c6263;
|
||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
@@ -2370,7 +2371,9 @@ form > .v2-extension-primary {
|
|||||||
|
|
||||||
.v2-gscu-config-form > .v2-extension-primary {
|
.v2-gscu-config-form > .v2-extension-primary {
|
||||||
min-width: 135px;
|
min-width: 135px;
|
||||||
margin: 0 0 22px;
|
height: 40px;
|
||||||
|
min-height: 40px;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-gscu-actions {
|
.v2-gscu-actions {
|
||||||
@@ -4104,6 +4107,14 @@ form > .v2-extension-primary {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v2-extension-dialog .v2-pet-card-region > header > button {
|
||||||
|
width: auto;
|
||||||
|
min-width: 104px;
|
||||||
|
height: 34px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
padding-inline: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
.v2-pet-card-region button:disabled,
|
.v2-pet-card-region button:disabled,
|
||||||
.v2-nfc-pet-binding button:disabled {
|
.v2-nfc-pet-binding button:disabled {
|
||||||
opacity: 0.52;
|
opacity: 0.52;
|
||||||
|
|||||||
@@ -732,12 +732,12 @@
|
|||||||
<header><div><h2>红外学习器 TCP 控制</h2></div><button type="button" data-close-extension-dialog="extensionGscuTcpDialog" aria-label="关闭"><i data-lucide="x"></i></button></header>
|
<header><div><h2>红外学习器 TCP 控制</h2></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>
|
<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">
|
<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>本机监听 IP</span><input id="extensionGscuTcpHost" inputmode="decimal" maxlength="45" placeholder="0.0.0.0" aria-describedby="extensionGscuTcpHostHint" required /><small class="visually-hidden" id="extensionGscuTcpHostHint">填写 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>
|
<label><span>监听端口</span><input id="extensionGscuTcpPort" type="number" min="1" max="65535" value="4444" aria-describedby="extensionGscuTcpPortHint" required /><small class="visually-hidden" id="extensionGscuTcpPortHint">必须与 GSCU1B 自定义 TCP 服务器端口一致。</small></label>
|
||||||
<button class="v2-extension-primary" type="submit" id="extensionGscuTcpApply">应用并开始监听</button>
|
<button class="v2-extension-primary" type="submit" id="extensionGscuTcpApply">应用并开始监听</button>
|
||||||
</form>
|
</form>
|
||||||
<section class="v2-gscu-actions">
|
<section class="v2-gscu-actions">
|
||||||
<header><div><strong>TCP 动作调试</strong><small>设备 ID 唯一标识控制对象;对象与动作可填写多个自然语言别名</small></div><label><span>红外槽位</span><input id="extensionGscuSlot" type="number" min="1" max="248" value="1" /></label><label><span>设备 ID</span><input id="extensionGscuDeviceId" maxlength="64" placeholder="toy-light-001" /></label><label><span>控制对象名称</span><input id="extensionGscuObjectNames" maxlength="240" placeholder="闪烁灯、闪光灯、闪灯玩具" /></label><label><span>执行动作</span><input id="extensionGscuActionNames" maxlength="240" placeholder="打开、开启、启动" /></label></header>
|
<header><div><strong>TCP 动作调试</strong></div><label><span>红外槽位</span><input id="extensionGscuSlot" type="number" min="1" max="248" value="1" /></label><label><span>设备 ID</span><input id="extensionGscuDeviceId" maxlength="64" placeholder="toy-light-001" /></label><label><span>控制对象名称</span><input id="extensionGscuObjectNames" maxlength="240" placeholder="闪烁灯、闪光灯、闪灯玩具" /></label><label><span>执行动作</span><input id="extensionGscuActionNames" maxlength="240" placeholder="打开、开启、启动" /></label></header>
|
||||||
<div class="v2-gscu-action-grid">
|
<div class="v2-gscu-action-grid">
|
||||||
<button type="button" data-gscu-action="info">查询设备</button>
|
<button type="button" data-gscu-action="info">查询设备</button>
|
||||||
<button type="button" data-gscu-action="learn">开始学习</button>
|
<button type="button" data-gscu-action="learn">开始学习</button>
|
||||||
@@ -757,12 +757,12 @@
|
|||||||
<label><span>恢复出厂确认</span><input id="extensionGscuDangerConfirm" autocomplete="off" placeholder="恢复出厂请输入 RESET_DEVICE" /></label>
|
<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>
|
<div><button class="danger" type="button" data-gscu-action="erase">擦除全部红外码</button><button class="danger" type="button" data-gscu-action="reset">恢复出厂设置</button></div>
|
||||||
</details>
|
</details>
|
||||||
<section class="v2-gscu-command-catalog"><header><strong>已学习红外信号</strong><small>按设备 ID 分组;对象名称用于自然语言识别和调试,动作请包含结果值,如“切换为蓝色”</small></header><div id="extensionGscuNamedCommands"></div></section>
|
<section class="v2-gscu-command-catalog"><header><strong>已学习红外信号</strong></header><div id="extensionGscuNamedCommands"></div></section>
|
||||||
<details class="v2-gscu-result" id="extensionGscuTcpResultDetails"><summary>JSON 调试结果</summary><pre id="extensionGscuTcpResult">等待操作</pre></details>
|
<details class="v2-gscu-result" id="extensionGscuTcpResultDetails"><summary>JSON 调试结果</summary><pre id="extensionGscuTcpResult">等待操作</pre></details>
|
||||||
</section>
|
</section>
|
||||||
</dialog>
|
</dialog>
|
||||||
<dialog class="v2-extension-dialog v2-pet-settings-dialog" id="extensionPetSettingsDialog" aria-labelledby="extensionPetSettingsTitle" aria-describedby="extensionPetSettingsDescription">
|
<dialog class="v2-extension-dialog v2-pet-settings-dialog" id="extensionPetSettingsDialog" aria-labelledby="extensionPetSettingsTitle" aria-describedby="extensionPetSettingsDescription">
|
||||||
<header><div><h2 id="extensionPetSettingsTitle">宠物设置</h2><p id="extensionPetSettingsDescription">添加或编辑宠物资料;保存后可在 NFC 设备授权中绑定卡片。</p></div><button type="button" data-close-extension-dialog="extensionPetSettingsDialog" data-dialog-close aria-label="关闭宠物设置"><i data-lucide="x"></i></button></header>
|
<header><div><h2 id="extensionPetSettingsTitle">宠物设置</h2><p class="visually-hidden" id="extensionPetSettingsDescription">添加或编辑宠物资料;保存后可在 NFC 设备授权中绑定卡片。</p></div><button type="button" data-close-extension-dialog="extensionPetSettingsDialog" data-dialog-close aria-label="关闭宠物设置"><i data-lucide="x"></i></button></header>
|
||||||
<div class="v2-pet-settings-body" id="extensionPetSettingsBody"></div>
|
<div class="v2-pet-settings-body" id="extensionPetSettingsBody"></div>
|
||||||
<footer class="v2-pet-settings-footnote"><i data-lucide="database"></i><span>宠物资料由后端永久保存,只有在此处手动删除才会移除。</span></footer>
|
<footer class="v2-pet-settings-footnote"><i data-lucide="database"></i><span>宠物资料由后端永久保存,只有在此处手动删除才会移除。</span></footer>
|
||||||
</dialog>
|
</dialog>
|
||||||
@@ -4085,12 +4085,12 @@
|
|||||||
</header>
|
</header>
|
||||||
<section class="v2-nfc-inline" data-state="${nfcState}">
|
<section class="v2-nfc-inline" data-state="${nfcState}">
|
||||||
<span class="v2-nfc-inline-icon"><i data-lucide="${nfcPet ? "paw-print" : nfcDevice ? "badge-check" : "scan-line"}"></i><b></b></span>
|
<span class="v2-nfc-inline-icon"><i data-lucide="${nfcPet ? "paw-print" : nfcDevice ? "badge-check" : "scan-line"}"></i><b></b></span>
|
||||||
<div><strong>${escapeHtml(nfcTitle)}</strong><p>${escapeHtml(nfcNote)}</p>${nfcCapabilities.length ? `<div>${nfcCapabilities.slice(0, 6).map((capability) => `<i>${escapeHtml(capability.name)}</i>`).join("")}</div>` : ""}</div>
|
<div><strong title="${escapeHtml(nfcNote)}">${escapeHtml(nfcTitle)}</strong>${nfcPet ? "" : `<p>${escapeHtml(nfcNote)}</p>`}${nfcCapabilities.length ? `<div>${nfcCapabilities.slice(0, 6).map((capability) => `<i>${escapeHtml(capability.name)}</i>`).join("")}</div>` : ""}</div>
|
||||||
<button type="button" data-open-nfc-binding>${nfcPet ? "查看宠物" : nfcDevice ? "查看授权" : nfcCard?.uid ? "关联卡片" : "开始感应"}</button>
|
<button type="button" data-open-nfc-binding>${nfcPet ? "查看宠物" : nfcDevice ? "查看授权" : nfcCard?.uid ? "关联卡片" : "开始感应"}</button>
|
||||||
</section>
|
</section>
|
||||||
<div class="v2-extension-gateway">
|
<div class="v2-extension-gateway">
|
||||||
<span><i data-lucide="usb"></i></span>
|
<span><i data-lucide="usb"></i></span>
|
||||||
<div><strong>${gatewayLabel}</strong><small>${escapeHtml(gatewayNote)}</small></div>
|
<div><strong title="${escapeHtml(gatewayNote)}">${gatewayLabel}</strong></div>
|
||||||
<b>${String(health.transport || "GSCU").toUpperCase()}</b>
|
<b>${String(health.transport || "GSCU").toUpperCase()}</b>
|
||||||
<button type="button" data-open-gscu-tcp><i data-lucide="settings-2"></i><span>TCP 设置</span></button>
|
<button type="button" data-open-gscu-tcp><i data-lucide="settings-2"></i><span>TCP 设置</span></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -4098,7 +4098,7 @@
|
|||||||
${nfcPet ? renderDetectedPetCard(nfcPet) : ""}
|
${nfcPet ? renderDetectedPetCard(nfcPet) : ""}
|
||||||
${devices.length ? devices.map((device) => `
|
${devices.length ? devices.map((device) => `
|
||||||
<article class="v2-extension-synced-device">
|
<article class="v2-extension-synced-device">
|
||||||
<header><span><i data-lucide="${device.cardUid ? "badge-check" : "cpu"}"></i></span><div><strong>${escapeHtml(device.name)}</strong><small>${escapeHtml(device.type)}${device.cardUid ? " · 已绑定 NFC" : ""}</small></div><b>${device.actions.length} 项能力</b></header>
|
<header><span><i data-lucide="${device.cardUid ? "badge-check" : "cpu"}"></i></span><div><strong title="${escapeHtml(device.type || device.name)}">${escapeHtml(device.name)}</strong></div><b>${device.actions.length} 项能力</b></header>
|
||||||
<div>${device.actions.slice(0, 8).map((action) => `<span>${escapeHtml(action)}</span>`).join("")}</div>
|
<div>${device.actions.slice(0, 8).map((action) => `<span>${escapeHtml(action)}</span>`).join("")}</div>
|
||||||
</article>`).join("") : nfcPet ? "" : `
|
</article>`).join("") : nfcPet ? "" : `
|
||||||
<div class="v2-extension-empty">
|
<div class="v2-extension-empty">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const CACHE_VERSION = "cognitive-agent-pad-v302";
|
const CACHE_VERSION = "cognitive-agent-pad-v305";
|
||||||
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
|
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
|
||||||
const RUNTIME_CACHE = `${CACHE_VERSION}-runtime`;
|
const RUNTIME_CACHE = `${CACHE_VERSION}-runtime`;
|
||||||
const APP_SHELL = [
|
const APP_SHELL = [
|
||||||
@@ -6,21 +6,21 @@ const APP_SHELL = [
|
|||||||
"/index.html",
|
"/index.html",
|
||||||
"/styles.css?v=dual-seat-23",
|
"/styles.css?v=dual-seat-23",
|
||||||
"/network.js?v=android-pad-2",
|
"/network.js?v=android-pad-2",
|
||||||
"/app.js?v=dual-seat-139",
|
"/app.js?v=dual-seat-140",
|
||||||
"/gesture-control.js?v=android-pad-9",
|
"/gesture-control.js?v=android-pad-9",
|
||||||
"/pwa.js?v=android-pad-3",
|
"/pwa.js?v=android-pad-3",
|
||||||
"/console-v2.css?v=delivery-state-69",
|
"/console-v2.css?v=delivery-state-69",
|
||||||
"/console-v2.js?v=local-first-70",
|
"/console-v2.js?v=local-first-70",
|
||||||
"/self-extension.css?v=pet-settings-49",
|
"/self-extension.css?v=pet-settings-52",
|
||||||
"/self-extension.js?v=pet-settings-49",
|
"/self-extension.js?v=pet-settings-52",
|
||||||
"/speech-input.css?v=sensevoice-7",
|
"/speech-input.css?v=sensevoice-7",
|
||||||
"/speech-input.js?v=sensevoice-13",
|
"/speech-input.js?v=sensevoice-13",
|
||||||
"/speech-audio-worklet.js?v=sensevoice-5",
|
"/speech-audio-worklet.js?v=sensevoice-5",
|
||||||
"/assistant-chat.css?v=codex-assistant-28",
|
"/assistant-chat.css?v=codex-assistant-29",
|
||||||
"/assistant-chat.js?v=codex-assistant-32",
|
"/assistant-chat.js?v=codex-assistant-33",
|
||||||
"/energy-monitor.css?v=energy-monitor-4",
|
"/energy-monitor.css?v=energy-monitor-4",
|
||||||
"/energy-monitor.js?v=energy-monitor-4",
|
"/energy-monitor.js?v=energy-monitor-4",
|
||||||
"/ui-consistency.css?v=spatial-ui-4",
|
"/ui-consistency.css?v=spatial-ui-5",
|
||||||
"/codepen-glass-theme.css?v=creative-cloud-2",
|
"/codepen-glass-theme.css?v=creative-cloud-2",
|
||||||
"/vue-ui.js?v=vue-presentation-3",
|
"/vue-ui.js?v=vue-presentation-3",
|
||||||
"/assets/pets/self-extension-cat.png?v=3",
|
"/assets/pets/self-extension-cat.png?v=3",
|
||||||
|
|||||||
@@ -2,12 +2,16 @@
|
|||||||
Keep geometry consistent across legacy modules without changing their
|
Keep geometry consistent across legacy modules without changing their
|
||||||
domain-specific colors, content density, or interaction behavior.
|
domain-specific colors, content density, or interaction behavior.
|
||||||
|
|
||||||
Hierarchy: control 10px -> item 12px -> section 16px -> panel 18px ->
|
Hierarchy: control 10px -> card 13px -> section 15px -> panel 16px ->
|
||||||
modal 22px. All modules consume the same semantic color tokens. */
|
modal 20px. All modules consume the same semantic color tokens. */
|
||||||
:root {
|
:root {
|
||||||
--ui-control-height-compact: 30px;
|
--ui-control-height-compact: 30px;
|
||||||
--ui-control-height: 38px;
|
--ui-control-height: 38px;
|
||||||
--ui-control-size-icon: 36px;
|
--ui-control-size-icon: 36px;
|
||||||
|
--ui-font-card-title: 13px;
|
||||||
|
--ui-font-section-title: 15px;
|
||||||
|
--ui-font-panel-title: 16px;
|
||||||
|
--ui-inline-action-width: 88px;
|
||||||
--ui-radius-control-inner: calc(var(--radius) - 2px);
|
--ui-radius-control-inner: calc(var(--radius) - 2px);
|
||||||
--ui-radius-control: var(--radius);
|
--ui-radius-control: var(--radius);
|
||||||
--ui-radius-item: calc(var(--radius) + 2px);
|
--ui-radius-item: calc(var(--radius) + 2px);
|
||||||
@@ -27,6 +31,43 @@
|
|||||||
--ui-focus-ring: 0 0 0 3px color-mix(in oklch, var(--ring) 22%, transparent);
|
--ui-focus-ring: 0 0 0 3px color-mix(in oklch, var(--ring) 22%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* One readable type scale prevents large legacy headings and tiny newer card
|
||||||
|
titles from competing inside the same workspace. */
|
||||||
|
.console-v2 :is(
|
||||||
|
.v2-operation-heading h2,
|
||||||
|
.joint-twin-shell .twin-heading h3,
|
||||||
|
.v2-device-sync-board > header h3,
|
||||||
|
.v2-extension-sync-heading h3,
|
||||||
|
.v2-sync-sidebar-heading h2,
|
||||||
|
.workspace-panel-title h3,
|
||||||
|
.config-panel-heading h3
|
||||||
|
) {
|
||||||
|
font-size: var(--ui-font-panel-title) !important;
|
||||||
|
line-height: 1.25;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-v2 :is(
|
||||||
|
.v2-pet-card-region > header h4,
|
||||||
|
.v2-gscu-actions > header strong,
|
||||||
|
.v2-gscu-command-catalog > header strong
|
||||||
|
) {
|
||||||
|
font-size: var(--ui-font-section-title) !important;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-v2 :is(
|
||||||
|
.v2-device-card-focus strong,
|
||||||
|
.v2-nfc-inline strong,
|
||||||
|
.v2-extension-gateway strong,
|
||||||
|
.v2-extension-synced-device header strong,
|
||||||
|
.v2-pet-presence-card > header strong,
|
||||||
|
.v2-pet-card > header > strong
|
||||||
|
) {
|
||||||
|
font-size: var(--ui-font-card-title) !important;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
/* Top-level workspace surfaces share one frame. */
|
/* Top-level workspace surfaces share one frame. */
|
||||||
.console-v2.v2-passive-display.v2-interactive-sync :is(
|
.console-v2.v2-passive-display.v2-interactive-sync :is(
|
||||||
.v2-operation-shell,
|
.v2-operation-shell,
|
||||||
@@ -174,6 +215,7 @@
|
|||||||
/* Repeated inline actions use one compact size. */
|
/* Repeated inline actions use one compact size. */
|
||||||
.console-v2 :is(
|
.console-v2 :is(
|
||||||
.v2-device-adjust-toggle,
|
.v2-device-adjust-toggle,
|
||||||
|
.v2-pet-settings-launch,
|
||||||
.v2-extension-nfc-launch,
|
.v2-extension-nfc-launch,
|
||||||
.v2-extension-gateway > button,
|
.v2-extension-gateway > button,
|
||||||
.v2-nfc-inline button
|
.v2-nfc-inline button
|
||||||
@@ -184,6 +226,36 @@
|
|||||||
border-radius: var(--ui-radius-control-inner);
|
border-radius: var(--ui-radius-control-inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Related extension actions use equal columns and a shared trailing action
|
||||||
|
width, so every card keeps the same horizontal rhythm. */
|
||||||
|
.console-v2 .v2-extension-sync-actions {
|
||||||
|
width: calc(var(--ui-inline-action-width) * 2 + 6px);
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-v2 .v2-extension-sync-actions > :is(
|
||||||
|
.v2-pet-settings-launch,
|
||||||
|
.v2-extension-nfc-launch
|
||||||
|
) {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
height: var(--ui-control-height-compact);
|
||||||
|
justify-content: center;
|
||||||
|
padding-inline: 7px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-v2 :is(
|
||||||
|
.v2-nfc-inline > button,
|
||||||
|
.v2-extension-gateway > button
|
||||||
|
) {
|
||||||
|
width: var(--ui-inline-action-width);
|
||||||
|
min-width: var(--ui-inline-action-width);
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* Header icon actions are distinct from circular record/send actions. */
|
/* Header icon actions are distinct from circular record/send actions. */
|
||||||
.assistant-drawer-actions .context-reset,
|
.assistant-drawer-actions .context-reset,
|
||||||
.assistant-drawer-close,
|
.assistant-drawer-close,
|
||||||
@@ -246,6 +318,7 @@
|
|||||||
.segmented > button,
|
.segmented > button,
|
||||||
.twin-selector > button,
|
.twin-selector > button,
|
||||||
.v2-device-adjust-toggle,
|
.v2-device-adjust-toggle,
|
||||||
|
.v2-pet-settings-launch,
|
||||||
.v2-extension-nfc-launch,
|
.v2-extension-nfc-launch,
|
||||||
.v2-extension-gateway > button,
|
.v2-extension-gateway > button,
|
||||||
.v2-nfc-inline button
|
.v2-nfc-inline button
|
||||||
@@ -263,6 +336,10 @@
|
|||||||
--ui-control-height-compact: 32px;
|
--ui-control-height-compact: 32px;
|
||||||
--ui-control-size-icon: 38px;
|
--ui-control-size-icon: 38px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.console-v2 .v2-extension-sync-actions {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
|||||||
@@ -807,7 +807,7 @@ operations:
|
|||||||
def test_static_console_and_runtime_endpoint(self) -> None:
|
def test_static_console_and_runtime_endpoint(self) -> None:
|
||||||
with self.opener.open(f"{self.base_url}/", timeout=3) as response:
|
with self.opener.open(f"{self.base_url}/", timeout=3) as response:
|
||||||
html = response.read().decode("utf-8")
|
html = response.read().decode("utf-8")
|
||||||
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-139", timeout=3) as response:
|
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-140", timeout=3) as response:
|
||||||
app_script = response.read().decode("utf-8")
|
app_script = response.read().decode("utf-8")
|
||||||
with self.opener.open(
|
with self.opener.open(
|
||||||
f"{self.base_url}/gesture-control.js?v=android-pad-9", timeout=3
|
f"{self.base_url}/gesture-control.js?v=android-pad-9", timeout=3
|
||||||
@@ -834,15 +834,15 @@ operations:
|
|||||||
) as response:
|
) as response:
|
||||||
speech_worklet = response.read().decode("utf-8")
|
speech_worklet = response.read().decode("utf-8")
|
||||||
with self.opener.open(
|
with self.opener.open(
|
||||||
f"{self.base_url}/self-extension.js?v=pet-settings-49", timeout=3
|
f"{self.base_url}/self-extension.js?v=pet-settings-52", timeout=3
|
||||||
) as response:
|
) as response:
|
||||||
extension_script = response.read().decode("utf-8")
|
extension_script = response.read().decode("utf-8")
|
||||||
with self.opener.open(
|
with self.opener.open(
|
||||||
f"{self.base_url}/self-extension.css?v=pet-settings-49", timeout=3
|
f"{self.base_url}/self-extension.css?v=pet-settings-52", timeout=3
|
||||||
) as response:
|
) as response:
|
||||||
extension_styles = response.read().decode("utf-8").replace("\r\n", "\n")
|
extension_styles = response.read().decode("utf-8").replace("\r\n", "\n")
|
||||||
with self.opener.open(
|
with self.opener.open(
|
||||||
f"{self.base_url}/assistant-chat.js?v=codex-assistant-32", timeout=3
|
f"{self.base_url}/assistant-chat.js?v=codex-assistant-33", timeout=3
|
||||||
) as response:
|
) as response:
|
||||||
assistant_script = response.read().decode("utf-8")
|
assistant_script = response.read().decode("utf-8")
|
||||||
with self.opener.open(
|
with self.opener.open(
|
||||||
@@ -887,7 +887,7 @@ operations:
|
|||||||
self.assertIn("CMD 0x431 · FB 0x41A · CH1", html)
|
self.assertIn("CMD 0x431 · FB 0x41A · CH1", html)
|
||||||
self.assertIn("CMD 0x431 · FB 0x388 · CH1", html)
|
self.assertIn("CMD 0x431 · FB 0x388 · CH1", html)
|
||||||
self.assertNotIn("透明车体 · 实体设备", html)
|
self.assertNotIn("透明车体 · 实体设备", html)
|
||||||
self.assertIn('src="/app.js?v=dual-seat-139"', html)
|
self.assertIn('src="/app.js?v=dual-seat-140"', html)
|
||||||
self.assertIn('href="/styles.css?v=dual-seat-23"', html)
|
self.assertIn('href="/styles.css?v=dual-seat-23"', html)
|
||||||
self.assertIn('src="/gesture-control.js?v=android-pad-9"', html)
|
self.assertIn('src="/gesture-control.js?v=android-pad-9"', html)
|
||||||
self.assertNotIn('class="vehicle-system-hud"', html)
|
self.assertNotIn('class="vehicle-system-hud"', html)
|
||||||
@@ -900,16 +900,16 @@ operations:
|
|||||||
self.assertIn('<body class="console-v2">', html)
|
self.assertIn('<body class="console-v2">', html)
|
||||||
self.assertIn('id="v2BootScreen"', html)
|
self.assertIn('id="v2BootScreen"', html)
|
||||||
self.assertIn('正在加载车机控制与实时状态', html)
|
self.assertIn('正在加载车机控制与实时状态', html)
|
||||||
self.assertIn('src="/self-extension.js?v=pet-settings-49"', html)
|
self.assertIn('src="/self-extension.js?v=pet-settings-52"', html)
|
||||||
self.assertIn('href="/self-extension.css?v=pet-settings-49"', html)
|
self.assertIn('href="/self-extension.css?v=pet-settings-52"', html)
|
||||||
self.assertNotIn("self-calibration", html)
|
self.assertNotIn("self-calibration", html)
|
||||||
self.assertNotIn("self-repair", html)
|
self.assertNotIn("self-repair", html)
|
||||||
self.assertNotIn("self-evolution", html)
|
self.assertNotIn("self-evolution", html)
|
||||||
self.assertNotIn("滚动查看更多", html)
|
self.assertNotIn("滚动查看更多", html)
|
||||||
self.assertIn('aria-label="发送消息"', html)
|
self.assertIn('aria-label="发送消息"', html)
|
||||||
self.assertIn('id="assistantTranscript"', html)
|
self.assertIn('id="assistantTranscript"', html)
|
||||||
self.assertIn('href="/assistant-chat.css?v=codex-assistant-28"', html)
|
self.assertIn('href="/assistant-chat.css?v=codex-assistant-29"', html)
|
||||||
self.assertIn('src="/assistant-chat.js?v=codex-assistant-32"', html)
|
self.assertIn('src="/assistant-chat.js?v=codex-assistant-33"', html)
|
||||||
self.assertIn('href="/energy-monitor.css?v=energy-monitor-4"', html)
|
self.assertIn('href="/energy-monitor.css?v=energy-monitor-4"', html)
|
||||||
self.assertIn('src="/energy-monitor.js?v=energy-monitor-4"', html)
|
self.assertIn('src="/energy-monitor.js?v=energy-monitor-4"', html)
|
||||||
self.assertIn('href="/codepen-glass-theme.css?v=creative-cloud-2"', html)
|
self.assertIn('href="/codepen-glass-theme.css?v=creative-cloud-2"', html)
|
||||||
@@ -929,7 +929,11 @@ operations:
|
|||||||
self.assertIn('setSidePanelVisibility("devices", false)', console_script)
|
self.assertIn('setSidePanelVisibility("devices", false)', console_script)
|
||||||
self.assertIn('setSidePanelVisibility("extensions", false)', console_script)
|
self.assertIn('setSidePanelVisibility("extensions", false)', console_script)
|
||||||
self.assertIn('setDrawerOpen(true)', assistant_script)
|
self.assertIn('setDrawerOpen(true)', assistant_script)
|
||||||
self.assertIn('($("#v2PanelControlDock") || document.body).append(trigger)', assistant_script)
|
self.assertIn('const panelDock = $("#v2PanelControlDock")', assistant_script)
|
||||||
|
self.assertIn(
|
||||||
|
"panelDock.insertBefore(trigger, firstRightPanelToggle)",
|
||||||
|
assistant_script,
|
||||||
|
)
|
||||||
self.assertIn('trigger.id = "energyMonitorToggle"', energy_script)
|
self.assertIn('trigger.id = "energyMonitorToggle"', energy_script)
|
||||||
self.assertIn('document.body.append(layer)', energy_script)
|
self.assertIn('document.body.append(layer)', energy_script)
|
||||||
self.assertIn('.energy-monitor-layer', energy_styles)
|
self.assertIn('.energy-monitor-layer', energy_styles)
|
||||||
@@ -1194,14 +1198,14 @@ operations:
|
|||||||
self.assertIn("CAN 指令已提交,等待车辆状态确认", console_script)
|
self.assertIn("CAN 指令已提交,等待车辆状态确认", console_script)
|
||||||
self.assertIn('canvas.addEventListener("webglcontextlost"', app_script)
|
self.assertIn('canvas.addEventListener("webglcontextlost"', app_script)
|
||||||
self.assertIn('"cognitive-agent:module-change"', app_script)
|
self.assertIn('"cognitive-agent:module-change"', app_script)
|
||||||
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v302"', service_worker)
|
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v305"', service_worker)
|
||||||
self.assertIn('/console-v2.css?v=delivery-state-69', service_worker)
|
self.assertIn('/console-v2.css?v=delivery-state-69', service_worker)
|
||||||
self.assertIn('/console-v2.js?v=local-first-70', service_worker)
|
self.assertIn('/console-v2.js?v=local-first-70', service_worker)
|
||||||
self.assertIn('/assistant-chat.css?v=codex-assistant-28', service_worker)
|
self.assertIn('/assistant-chat.css?v=codex-assistant-29', service_worker)
|
||||||
self.assertIn('/assistant-chat.js?v=codex-assistant-32', service_worker)
|
self.assertIn('/assistant-chat.js?v=codex-assistant-33', service_worker)
|
||||||
self.assertIn('/energy-monitor.css?v=energy-monitor-4', service_worker)
|
self.assertIn('/energy-monitor.css?v=energy-monitor-4', service_worker)
|
||||||
self.assertIn('/energy-monitor.js?v=energy-monitor-4', service_worker)
|
self.assertIn('/energy-monitor.js?v=energy-monitor-4', service_worker)
|
||||||
self.assertIn('"/app.js?v=dual-seat-139"', service_worker)
|
self.assertIn('"/app.js?v=dual-seat-140"', service_worker)
|
||||||
self.assertIn('"/speech-input.js?v=sensevoice-13"', service_worker)
|
self.assertIn('"/speech-input.js?v=sensevoice-13"', service_worker)
|
||||||
self.assertIn('"/speech-input.css?v=sensevoice-7"', service_worker)
|
self.assertIn('"/speech-input.css?v=sensevoice-7"', service_worker)
|
||||||
self.assertIn('"/speech-audio-worklet.js?v=sensevoice-5"', service_worker)
|
self.assertIn('"/speech-audio-worklet.js?v=sensevoice-5"', service_worker)
|
||||||
@@ -1658,7 +1662,7 @@ operations:
|
|||||||
) as response:
|
) as response:
|
||||||
extension_script = response.read().decode("utf-8")
|
extension_script = response.read().decode("utf-8")
|
||||||
with self.opener.open(
|
with self.opener.open(
|
||||||
f"{self.base_url}/app.js?v=dual-seat-139", timeout=3
|
f"{self.base_url}/app.js?v=dual-seat-140", timeout=3
|
||||||
) as response:
|
) as response:
|
||||||
app_script = response.read().decode("utf-8")
|
app_script = response.read().decode("utf-8")
|
||||||
|
|
||||||
|
|||||||
@@ -13,15 +13,15 @@ def test_console_contains_ai_assistant_surface():
|
|||||||
assert 'aria-label="AI助手对话"' in html
|
assert 'aria-label="AI助手对话"' in html
|
||||||
assert 'placeholder="向 AI助手提问或交代工作"' in html
|
assert 'placeholder="向 AI助手提问或交代工作"' in html
|
||||||
assert 'aria-label="发送消息"' in html
|
assert 'aria-label="发送消息"' in html
|
||||||
assert 'href="/assistant-chat.css?v=codex-assistant-28"' in html
|
assert 'href="/assistant-chat.css?v=codex-assistant-29"' in html
|
||||||
assert 'src="/assistant-chat.js?v=codex-assistant-32"' in html
|
assert 'src="/assistant-chat.js?v=codex-assistant-33"' in html
|
||||||
|
|
||||||
|
|
||||||
def test_codex_assistant_assets_are_versioned_together_in_the_service_worker():
|
def test_codex_assistant_assets_are_versioned_together_in_the_service_worker():
|
||||||
service_worker = (ROOT / "apps/web-console/service-worker.js").read_text(encoding="utf-8")
|
service_worker = (ROOT / "apps/web-console/service-worker.js").read_text(encoding="utf-8")
|
||||||
assert 'const CACHE_VERSION = "cognitive-agent-pad-v302"' in service_worker
|
assert 'const CACHE_VERSION = "cognitive-agent-pad-v305"' in service_worker
|
||||||
assert '"/assistant-chat.css?v=codex-assistant-28"' in service_worker
|
assert '"/assistant-chat.css?v=codex-assistant-29"' in service_worker
|
||||||
assert '"/assistant-chat.js?v=codex-assistant-32"' in service_worker
|
assert '"/assistant-chat.js?v=codex-assistant-33"' in service_worker
|
||||||
|
|
||||||
|
|
||||||
def test_codex_frontend_uses_one_streaming_endpoint_and_agent_work_events():
|
def test_codex_frontend_uses_one_streaming_endpoint_and_agent_work_events():
|
||||||
@@ -121,7 +121,11 @@ def test_starting_a_new_codex_conversation_rotates_the_conversation_id():
|
|||||||
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
|
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
|
||||||
assert 'const CONVERSATION_STORAGE_KEY = "cognitive-agent.conversation-id"' in script
|
assert 'const CONVERSATION_STORAGE_KEY = "cognitive-agent.conversation-id"' in script
|
||||||
assert "function createConversationId()" in script
|
assert "function createConversationId()" in script
|
||||||
assert "sessionStorage.setItem(CONVERSATION_STORAGE_KEY, createConversationId())" in script
|
assert "const nextConversationId = createConversationId()" in script
|
||||||
|
assert (
|
||||||
|
"sessionStorage.setItem(CONVERSATION_STORAGE_KEY, nextConversationId)"
|
||||||
|
in script
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_assistant_uses_the_shared_top_right_dock_and_opens_by_default():
|
def test_assistant_uses_the_shared_top_right_dock_and_opens_by_default():
|
||||||
@@ -137,7 +141,9 @@ def test_assistant_uses_the_shared_top_right_dock_and_opens_by_default():
|
|||||||
assert 'trigger.setAttribute("aria-pressed", String(open))' in script
|
assert 'trigger.setAttribute("aria-pressed", String(open))' in script
|
||||||
assert 'drawer.toggleAttribute("inert", !open)' in script
|
assert 'drawer.toggleAttribute("inert", !open)' in script
|
||||||
assert 'trigger.className = "assistant-drawer-toggle v2-panel-control-button"' in script
|
assert 'trigger.className = "assistant-drawer-toggle v2-panel-control-button"' in script
|
||||||
assert '($("#v2PanelControlDock") || document.body).append(trigger)' in script
|
assert 'const panelDock = $("#v2PanelControlDock")' in script
|
||||||
|
assert "panelDock.insertBefore(trigger, firstRightPanelToggle)" in script
|
||||||
|
assert "document.body.append(trigger)" in script
|
||||||
assert 'id = "assistantDrawerClose"' not in script
|
assert 'id = "assistantDrawerClose"' not in script
|
||||||
assert "setDrawerOpen(true)" in script
|
assert "setDrawerOpen(true)" in script
|
||||||
assert 'event.key === "Escape"' in script
|
assert 'event.key === "Escape"' in script
|
||||||
@@ -252,8 +258,8 @@ def test_latest_same_name_mode_draft_retires_stale_card_and_lifecycle_clears_con
|
|||||||
"async function tryModeProfileDraft", 1
|
"async function tryModeProfileDraft", 1
|
||||||
)[0]
|
)[0]
|
||||||
new_conversation_flow = assistant.split(
|
new_conversation_flow = assistant.split(
|
||||||
'if (!target?.closest("#newConversation")) return;', 1
|
"function resetConversation()", 1
|
||||||
)[1].split("}, true);", 1)[0]
|
)[1].split("function initialize()", 1)[0]
|
||||||
|
|
||||||
assert "card.dataset.assistantModeName !== nextName" in replace_flow
|
assert "card.dataset.assistantModeName !== nextName" in replace_flow
|
||||||
assert "card.dataset.assistantModeDraft === nextRequestId" in replace_flow
|
assert "card.dataset.assistantModeDraft === nextRequestId" in replace_flow
|
||||||
@@ -266,6 +272,32 @@ def test_latest_same_name_mode_draft_retires_stale_card_and_lifecycle_clears_con
|
|||||||
assert ".assistant-mode-draft-status.superseded" in css
|
assert ".assistant-mode-draft-status.superseded" in css
|
||||||
|
|
||||||
|
|
||||||
|
def test_new_conversation_resets_chat_in_place_and_keeps_panel_order_spatial():
|
||||||
|
app = (ROOT / "apps/web-console/app.js").read_text(encoding="utf-8")
|
||||||
|
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
|
reset_flow = assistant.split("function resetConversation()", 1)[1].split(
|
||||||
|
"function initialize()", 1
|
||||||
|
)[0]
|
||||||
|
app_flow = app.split("function startNewConversation", 1)[1].split(
|
||||||
|
"window.CognitiveAgentConsole", 1
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
assert 'activeController.abort("new_conversation")' in reset_flow
|
||||||
|
assert "sessionStorage.removeItem(STORAGE_KEY)" in reset_flow
|
||||||
|
assert "renderInitialMessages()" in reset_flow
|
||||||
|
assert "setComposerPending(false)" in reset_flow
|
||||||
|
assert '"cognitive-agent:new-conversation"' in reset_flow
|
||||||
|
assert "window.location.reload()" not in app_flow
|
||||||
|
assert (
|
||||||
|
'window.addEventListener("cognitive-agent:new-conversation", '
|
||||||
|
"startNewConversation)"
|
||||||
|
) in app
|
||||||
|
assert 'const firstRightPanelToggle = $("[data-panel-toggle]", panelDock)' in assistant
|
||||||
|
assert "panelDock.insertBefore(trigger, firstRightPanelToggle)" in assistant
|
||||||
|
|
||||||
|
|
||||||
def test_assistant_composer_grows_to_a_cap_then_scrolls_its_own_content():
|
def test_assistant_composer_grows_to_a_cap_then_scrolls_its_own_content():
|
||||||
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
|
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
|
||||||
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
|
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
|
||||||
@@ -383,6 +415,18 @@ def test_chat_styles_include_accessible_responsive_and_reduced_motion_states():
|
|||||||
assert "@media (prefers-reduced-motion: reduce)" in css
|
assert "@media (prefers-reduced-motion: reduce)" in css
|
||||||
|
|
||||||
|
|
||||||
|
def test_dispatched_control_card_spans_the_full_assistant_message_row():
|
||||||
|
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
|
||||||
|
layout = css.split("/* Compact command receipt", 1)[1].split(
|
||||||
|
"/* The function-mode strip", 1
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
assert "grid-template-columns: 30px minmax(0, 1fr)" in layout
|
||||||
|
assert "display: contents" in layout
|
||||||
|
assert "grid-column: 1 / -1" in layout
|
||||||
|
assert "max-width: none" in layout
|
||||||
|
|
||||||
|
|
||||||
def test_assistant_does_not_restore_collapsed_sidebars_and_uses_mobile_overlays():
|
def test_assistant_does_not_restore_collapsed_sidebars_and_uses_mobile_overlays():
|
||||||
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
|
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
|
||||||
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
|
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ def test_energy_monitor_assets_are_versioned_and_cached() -> None:
|
|||||||
):
|
):
|
||||||
assert asset in html
|
assert asset in html
|
||||||
assert f'"{asset}"' in service_worker
|
assert f'"{asset}"' in service_worker
|
||||||
assert html.index("/assistant-chat.js?v=codex-assistant-32") < html.index(
|
assert html.index("/assistant-chat.js?v=codex-assistant-33") < html.index(
|
||||||
"/energy-monitor.js?v=energy-monitor-4"
|
"/energy-monitor.js?v=energy-monitor-4"
|
||||||
)
|
)
|
||||||
assert 'const CACHE_VERSION = "cognitive-agent-pad-v302"' in service_worker
|
assert 'const CACHE_VERSION = "cognitive-agent-pad-v305"' in service_worker
|
||||||
|
|
||||||
|
|
||||||
def test_energy_monitor_uses_the_fifth_shared_dock_icon_and_starts_closed() -> None:
|
def test_energy_monitor_uses_the_fifth_shared_dock_icon_and_starts_closed() -> None:
|
||||||
|
|||||||
@@ -42,13 +42,13 @@ def test_theme_asset_versions_are_consistent_with_the_pwa_shell() -> None:
|
|||||||
for asset in (
|
for asset in (
|
||||||
"/styles.css?v=dual-seat-23",
|
"/styles.css?v=dual-seat-23",
|
||||||
"/console-v2.css?v=delivery-state-69",
|
"/console-v2.css?v=delivery-state-69",
|
||||||
"/self-extension.css?v=pet-settings-49",
|
"/self-extension.css?v=pet-settings-52",
|
||||||
"/speech-input.css?v=sensevoice-7",
|
"/speech-input.css?v=sensevoice-7",
|
||||||
"/codepen-glass-theme.css?v=creative-cloud-2",
|
"/codepen-glass-theme.css?v=creative-cloud-2",
|
||||||
):
|
):
|
||||||
assert asset in html
|
assert asset in html
|
||||||
assert f'"{asset}"' in service_worker
|
assert f'"{asset}"' in service_worker
|
||||||
assert 'const CACHE_VERSION = "cognitive-agent-pad-v302"' in service_worker
|
assert 'const CACHE_VERSION = "cognitive-agent-pad-v305"' in service_worker
|
||||||
|
|
||||||
|
|
||||||
def test_reference_glass_theme_covers_the_complete_workspace() -> None:
|
def test_reference_glass_theme_covers_the_complete_workspace() -> None:
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ def test_unified_console_exposes_device_id_based_nfc_authorization() -> None:
|
|||||||
assert '.v2-nfc-command-list' in styles
|
assert '.v2-nfc-command-list' in styles
|
||||||
assert '.v2-nfc-command-option' in styles
|
assert '.v2-nfc-command-option' in styles
|
||||||
assert '.v2-nfc-select-all' in styles
|
assert '.v2-nfc-select-all' in styles
|
||||||
assert 'self-extension.js?v=pet-settings-49' in html
|
assert 'self-extension.js?v=pet-settings-52' in html
|
||||||
assert 'self-extension.css?v=pet-settings-49' in html
|
assert 'self-extension.css?v=pet-settings-52' in html
|
||||||
|
|
||||||
|
|
||||||
def test_unified_console_exposes_gscu_tcp_configuration_and_actions() -> None:
|
def test_unified_console_exposes_gscu_tcp_configuration_and_actions() -> None:
|
||||||
|
|||||||
@@ -1110,7 +1110,8 @@ def test_interactive_console_uses_one_top_right_icon_dock_for_all_five_panels()
|
|||||||
assert 'data-lucide="car-front"' in console
|
assert 'data-lucide="car-front"' in console
|
||||||
assert 'data-lucide="usb"' in console
|
assert 'data-lucide="usb"' in console
|
||||||
assert 'data-lucide="bot-message-square"' in assistant
|
assert 'data-lucide="bot-message-square"' in assistant
|
||||||
assert '($("#v2PanelControlDock") || document.body).append(trigger)' in assistant
|
assert 'const panelDock = $("#v2PanelControlDock")' in assistant
|
||||||
|
assert "panelDock.insertBefore(trigger, firstRightPanelToggle)" in assistant
|
||||||
assert 'trigger.className = "assistant-drawer-toggle v2-panel-control-button"' in assistant
|
assert 'trigger.className = "assistant-drawer-toggle v2-panel-control-button"' in assistant
|
||||||
assert 'trigger.className = "energy-monitor-toggle v2-panel-control-button"' in energy
|
assert 'trigger.className = "energy-monitor-toggle v2-panel-control-button"' in energy
|
||||||
assert '$("#v2PanelControlDock").append(trigger)' in energy
|
assert '$("#v2PanelControlDock").append(trigger)' in energy
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ def source(name: str) -> str:
|
|||||||
def test_spatial_ui_contract_is_loaded_last_and_cached() -> None:
|
def test_spatial_ui_contract_is_loaded_last_and_cached() -> None:
|
||||||
html = source("index.html")
|
html = source("index.html")
|
||||||
service_worker = source("service-worker.js")
|
service_worker = source("service-worker.js")
|
||||||
asset = "/ui-consistency.css?v=spatial-ui-4"
|
asset = "/ui-consistency.css?v=spatial-ui-5"
|
||||||
|
|
||||||
assert asset in html
|
assert asset in html
|
||||||
assert f'"{asset}"' in service_worker
|
assert f'"{asset}"' in service_worker
|
||||||
@@ -25,6 +25,10 @@ def test_same_kind_surfaces_share_geometry_tokens() -> None:
|
|||||||
for token in (
|
for token in (
|
||||||
"--ui-control-height-compact:",
|
"--ui-control-height-compact:",
|
||||||
"--ui-control-height:",
|
"--ui-control-height:",
|
||||||
|
"--ui-font-card-title:",
|
||||||
|
"--ui-font-section-title:",
|
||||||
|
"--ui-font-panel-title:",
|
||||||
|
"--ui-inline-action-width:",
|
||||||
"--ui-radius-control:",
|
"--ui-radius-control:",
|
||||||
"--ui-radius-item:",
|
"--ui-radius-item:",
|
||||||
"--ui-radius-section:",
|
"--ui-radius-section:",
|
||||||
@@ -59,6 +63,7 @@ def test_same_kind_controls_share_sizes_and_focus_treatment() -> None:
|
|||||||
"#v2PanelControlDock > .v2-panel-control-button",
|
"#v2PanelControlDock > .v2-panel-control-button",
|
||||||
".v2-mode-rail .mode-rail-actions > button",
|
".v2-mode-rail .mode-rail-actions > button",
|
||||||
".v2-device-adjust-toggle",
|
".v2-device-adjust-toggle",
|
||||||
|
".v2-pet-settings-launch",
|
||||||
".v2-extension-nfc-launch",
|
".v2-extension-nfc-launch",
|
||||||
".energy-trend-tabs button",
|
".energy-trend-tabs button",
|
||||||
".mode-gallery-close",
|
".mode-gallery-close",
|
||||||
@@ -71,6 +76,44 @@ def test_same_kind_controls_share_sizes_and_focus_treatment() -> None:
|
|||||||
assert "outline: none" in styles
|
assert "outline: none" in styles
|
||||||
|
|
||||||
|
|
||||||
|
def test_panel_titles_and_extension_actions_share_type_and_size_tokens() -> None:
|
||||||
|
styles = source("ui-consistency.css")
|
||||||
|
|
||||||
|
for selector in (
|
||||||
|
".v2-operation-heading h2",
|
||||||
|
".joint-twin-shell .twin-heading h3",
|
||||||
|
".v2-device-sync-board > header h3",
|
||||||
|
".v2-extension-sync-heading h3",
|
||||||
|
".v2-device-card-focus strong",
|
||||||
|
".v2-nfc-inline strong",
|
||||||
|
".v2-extension-gateway strong",
|
||||||
|
):
|
||||||
|
assert selector in styles
|
||||||
|
|
||||||
|
assert "font-size: var(--ui-font-panel-title) !important" in styles
|
||||||
|
assert "font-size: var(--ui-font-card-title) !important" in styles
|
||||||
|
assert "grid-template-columns: repeat(2, minmax(0, 1fr))" in styles
|
||||||
|
assert "width: var(--ui-inline-action-width)" in styles
|
||||||
|
|
||||||
|
|
||||||
|
def test_pet_and_tcp_dialog_controls_keep_accessible_aligned_layout() -> None:
|
||||||
|
styles = source("self-extension.css")
|
||||||
|
extension = source("self-extension.js")
|
||||||
|
tcp_form = styles.split(".v2-gscu-config-form {", 1)[1].split("}", 1)[0]
|
||||||
|
tcp_submit = styles.split(
|
||||||
|
".v2-gscu-config-form > .v2-extension-primary {", 1
|
||||||
|
)[1].split("}", 1)[0]
|
||||||
|
|
||||||
|
assert ".v2-extension-dialog .v2-pet-card-region > header > button" in styles
|
||||||
|
assert "min-width: 104px" in styles
|
||||||
|
assert "align-items: end" in tcp_form
|
||||||
|
assert "height: 40px" in tcp_submit
|
||||||
|
assert "margin: 0" in tcp_submit
|
||||||
|
assert 'id="extensionGscuTcpHostHint"' in extension
|
||||||
|
assert 'id="extensionGscuTcpPortHint"' in extension
|
||||||
|
assert 'class="visually-hidden" id="extensionPetSettingsDescription"' in extension
|
||||||
|
|
||||||
|
|
||||||
def test_compact_sidebar_overrides_cannot_break_shared_geometry() -> None:
|
def test_compact_sidebar_overrides_cannot_break_shared_geometry() -> None:
|
||||||
styles = source("ui-consistency.css")
|
styles = source("ui-consistency.css")
|
||||||
|
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ def test_vue_presentation_assets_are_local_ordered_and_cached() -> None:
|
|||||||
presentation = "/vue-ui.js?v=vue-presentation-3"
|
presentation = "/vue-ui.js?v=vue-presentation-3"
|
||||||
|
|
||||||
assert html.index(runtime) < html.index(presentation) < html.index(
|
assert html.index(runtime) < html.index(presentation) < html.index(
|
||||||
"/app.js?v=dual-seat-139"
|
"/app.js?v=dual-seat-140"
|
||||||
)
|
)
|
||||||
assert html.index(presentation) < html.index(
|
assert html.index(presentation) < html.index(
|
||||||
"/assistant-chat.js?v=codex-assistant-32"
|
"/assistant-chat.js?v=codex-assistant-33"
|
||||||
)
|
)
|
||||||
assert f'"{runtime}"' in service_worker
|
assert f'"{runtime}"' in service_worker
|
||||||
assert f'"{presentation}"' in service_worker
|
assert f'"{presentation}"' in service_worker
|
||||||
assert 'const CACHE_VERSION = "cognitive-agent-pad-v302"' in service_worker
|
assert 'const CACHE_VERSION = "cognitive-agent-pad-v305"' in service_worker
|
||||||
assert "unpkg.com" not in html
|
assert "unpkg.com" not in html
|
||||||
assert "cdn.jsdelivr.net" not in html
|
assert "cdn.jsdelivr.net" not in html
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user