fix: move page ai launcher to pi lab
This commit is contained in:
@@ -4624,15 +4624,29 @@
|
|||||||
|
|
||||||
function installLauncher() {
|
function installLauncher() {
|
||||||
if (piLabLauncherEl) return;
|
if (piLabLauncherEl) return;
|
||||||
piLabLauncherEl = el('button', {
|
var hostButton = document.querySelector('[data-mnote-action="open-page-ai-pi-lab"], [data-testid="wolai-floating-ai"]');
|
||||||
type: 'button',
|
if (hostButton instanceof HTMLElement) {
|
||||||
className: 'wolai-page-ai-pi-lab-launcher',
|
piLabLauncherEl = hostButton;
|
||||||
'data-page-ai-pi-lab-launcher': 'true',
|
piLabLauncherEl.setAttribute('data-page-ai-pi-lab-launcher', 'true');
|
||||||
title: 'Pi Lab',
|
piLabLauncherEl.setAttribute('data-mnote-action', 'open-page-ai-pi-lab');
|
||||||
'aria-label': '打开 Pi Lab',
|
piLabLauncherEl.setAttribute('aria-label', '打开 Pi Lab');
|
||||||
}, 'π');
|
piLabLauncherEl.setAttribute('title', '打开 Pi Lab');
|
||||||
piLabLauncherEl.addEventListener('click', showPiLab);
|
piLabLauncherEl.textContent = 'π';
|
||||||
document.body.appendChild(piLabLauncherEl);
|
} else {
|
||||||
|
piLabLauncherEl = el('button', {
|
||||||
|
type: 'button',
|
||||||
|
className: 'wolai-page-ai-pi-lab-launcher',
|
||||||
|
'data-page-ai-pi-lab-launcher': 'true',
|
||||||
|
title: 'Pi Lab',
|
||||||
|
'aria-label': '打开 Pi Lab',
|
||||||
|
}, 'π');
|
||||||
|
document.body.appendChild(piLabLauncherEl);
|
||||||
|
}
|
||||||
|
piLabLauncherEl.addEventListener('click', function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
showPiLab();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function wireEvents() {
|
function wireEvents() {
|
||||||
|
|||||||
@@ -880,9 +880,9 @@ export function createSidebarPageAiRuntime(context) {
|
|||||||
|
|
||||||
function pageAiOpencodeHostEnabled() {
|
function pageAiOpencodeHostEnabled() {
|
||||||
try {
|
try {
|
||||||
return window.localStorage ? window.localStorage.getItem('mnote.page_ai.opencode_host') !== '0' : true;
|
return window.localStorage ? window.localStorage.getItem('mnote.page_ai.opencode_host') === '1' : false;
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3191,6 +3191,16 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pageAiPiLabTrigger = closestAction(e.target, '[data-mnote-action="open-page-ai-pi-lab"]');
|
||||||
|
if (pageAiPiLabTrigger) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (window.createSidebarPageAiPiLabRuntime) {
|
||||||
|
window.createSidebarPageAiPiLabRuntime({});
|
||||||
|
}
|
||||||
|
window.postMessage({ source: 'mnote-sidebar', type: 'mnote:pi-lab-show' }, window.location.origin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var navigationPageTrigger = closestAction(e.target, '[data-mnote-action="open-navigation-page"]');
|
var navigationPageTrigger = closestAction(e.target, '[data-mnote-action="open-navigation-page"]');
|
||||||
if (navigationPageTrigger) {
|
if (navigationPageTrigger) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -4159,7 +4159,7 @@ mod tests {
|
|||||||
assert!(!html.contains("[data-node-id=\"${escapedId}\"] .tree-link-title"));
|
assert!(!html.contains("[data-node-id=\"${escapedId}\"] .tree-link-title"));
|
||||||
assert!(html.contains("data-testid=\"wolai-page-settings-trigger\""));
|
assert!(html.contains("data-testid=\"wolai-page-settings-trigger\""));
|
||||||
assert!(html.contains("data-mnote-action=\"open-page-settings\""));
|
assert!(html.contains("data-mnote-action=\"open-page-settings\""));
|
||||||
assert!(html.contains("data-mnote-action=\"open-page-ai\""));
|
assert!(html.contains("data-mnote-action=\"open-page-ai-pi-lab\""));
|
||||||
assert!(html.contains("\"titleEndpoint\":\"/api/documents/title\""));
|
assert!(html.contains("\"titleEndpoint\":\"/api/documents/title\""));
|
||||||
assert!(html.contains("data-testid=\"mnote-document-workspace\""));
|
assert!(html.contains("data-testid=\"mnote-document-workspace\""));
|
||||||
assert!(html.contains("data-document-pane=\"true\""));
|
assert!(html.contains("data-document-pane=\"true\""));
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ pub fn PageLayout(
|
|||||||
</article>
|
</article>
|
||||||
<div class="wolai-floating-actions" aria-label="浮动操作">
|
<div class="wolai-floating-actions" aria-label="浮动操作">
|
||||||
<a href="/knowledge" data-testid="mnote-floating-knowledge" class="wolai-floating-button wolai-floating-button--knowledge" aria-label="知识库" title="打开知识库" data-mnote-action="open-knowledge-host"><span class="material-symbols-outlined material-symbols-filled" data-icon="travel_explore" aria-hidden="true"></span></a>
|
<a href="/knowledge" data-testid="mnote-floating-knowledge" class="wolai-floating-button wolai-floating-button--knowledge" aria-label="知识库" title="打开知识库" data-mnote-action="open-knowledge-host"><span class="material-symbols-outlined material-symbols-filled" data-icon="travel_explore" aria-hidden="true"></span></a>
|
||||||
<button type="button" data-testid="wolai-floating-ai" class="wolai-floating-button wolai-floating-button--ai" aria-label="AI 助手" data-state="closed" aria-haspopup="dialog" aria-expanded="false" title="点击 进入空间智能问答" data-mnote-action="open-page-ai"><span class="material-symbols-outlined material-symbols-filled" data-icon="auto_awesome" aria-hidden="true"></span></button>
|
<button type="button" data-testid="wolai-floating-ai" class="wolai-floating-button wolai-floating-button--ai wolai-floating-button--pi" aria-label="Pi Lab" data-state="closed" aria-haspopup="dialog" aria-expanded="false" title="打开 Pi Lab" data-mnote-action="open-page-ai-pi-lab">{"\u{03c0}"}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="mnote-portal-root" data-mnote-portal-root="true"></div>
|
<div id="mnote-portal-root" data-mnote-portal-root="true"></div>
|
||||||
@@ -544,6 +544,8 @@ mod tests {
|
|||||||
html.find(r#"data-testid="mnote-floating-knowledge""#)
|
html.find(r#"data-testid="mnote-floating-knowledge""#)
|
||||||
< html.find(r#"data-testid="wolai-floating-ai""#)
|
< html.find(r#"data-testid="wolai-floating-ai""#)
|
||||||
);
|
);
|
||||||
|
assert!(html.contains(r#"data-mnote-action="open-page-ai-pi-lab""#));
|
||||||
|
assert!(!html.contains(r#"data-mnote-action="open-page-ai""#));
|
||||||
assert!(!html.contains(r#"data-mnote-action="open-knowledge-rag-settings""#));
|
assert!(!html.contains(r#"data-mnote-action="open-knowledge-rag-settings""#));
|
||||||
assert!(html.contains(r#"data-icon="travel_explore""#));
|
assert!(html.contains(r#"data-icon="travel_explore""#));
|
||||||
assert!(crate::ssr::styles::MNOTE_CSS.contains(r#"data-icon="travel_explore""#));
|
assert!(crate::ssr::styles::MNOTE_CSS.contains(r#"data-icon="travel_explore""#));
|
||||||
|
|||||||
@@ -2383,6 +2383,10 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wolai-floating-button--pi {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
.document-shell[data-page-wide-layout="true"] {
|
.document-shell[data-page-wide-layout="true"] {
|
||||||
width: min(100%, 980px);
|
width: min(100%, 980px);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ node scripts/task167-local-markdown-title-body-options-no-convex-smoke.js
|
|||||||
node scripts/task490-runtime-surfaces-smoke.js
|
node scripts/task490-runtime-surfaces-smoke.js
|
||||||
```
|
```
|
||||||
|
|
||||||
其中 `task490-runtime-surfaces-smoke.js` 是轻量浏览器 smoke,验证页面设置、Page AI drawer 打开/关闭、Page AI stop-run abort、slash menu、block handle menu 能在当前 leptos-tiptap 文档页工作;它使用 mock run 覆盖停止按钮,不要求真实模型返回,也不替代更重的页面设置、Pi Lab/Page AI 或编辑器菜单专项脚本。
|
其中 `task490-runtime-surfaces-smoke.js` 是轻量浏览器 smoke,验证页面设置、浮动 Pi Lab 入口、旧 OpenHub/opencode 前端不再打开、slash menu、block handle menu 能在当前 leptos-tiptap 文档页工作;它不要求真实模型返回,也不替代更重的 Pi Lab/Page AI 或编辑器菜单专项脚本。
|
||||||
|
|
||||||
### 0.1 当前主链优先脚本
|
### 0.1 当前主链优先脚本
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* - ENABLE_BACKEND:设为 "1" or "true" 时启用默认 FastAPI 后端
|
* - ENABLE_BACKEND:设为 "1" or "true" 时启用默认 FastAPI 后端
|
||||||
* - BACKEND_CMD:覆盖 FastAPI 启动命令;设置后即视为显式启用后端
|
* - BACKEND_CMD:覆盖 FastAPI 启动命令;设置后即视为显式启用后端
|
||||||
* - SKIP_BACKEND:设为 "1" or "true" 可强制跳过 FastAPI 后端
|
* - SKIP_BACKEND:设为 "1" or "true" 可强制跳过 FastAPI 后端
|
||||||
* - ENABLE_OPENCODE:设为 "1" or "true" 时启用 opencode serve
|
* - ENABLE_OPENCODE:设为 "1" or "true" 时启用 opencode serve;默认不启动
|
||||||
* - OPENCODE_CMD:覆盖 opencode 启动命令;设置后即视为显式启用 opencode
|
* - OPENCODE_CMD:覆盖 opencode 启动命令;设置后即视为显式启用 opencode
|
||||||
* - SKIP_OPENCODE:设为 "1" or "true" 可强制跳过 opencode
|
* - SKIP_OPENCODE:设为 "1" or "true" 可强制跳过 opencode
|
||||||
* - MNOTE_OPENCODE_XDG_ROOT / MNOTE_OPENCODE_HOME:opencode 专用运行目录
|
* - MNOTE_OPENCODE_XDG_ROOT / MNOTE_OPENCODE_HOME:opencode 专用运行目录
|
||||||
@@ -128,7 +128,7 @@ function shouldStartBackend(env = process.env) {
|
|||||||
function shouldStartOpencode(env = process.env) {
|
function shouldStartOpencode(env = process.env) {
|
||||||
if (isEnabledEnv(env.SKIP_OPENCODE)) return false;
|
if (isEnabledEnv(env.SKIP_OPENCODE)) return false;
|
||||||
if (String(env.OPENCODE_CMD || "").trim()) return true;
|
if (String(env.OPENCODE_CMD || "").trim()) return true;
|
||||||
return true;
|
return isEnabledEnv(env.ENABLE_OPENCODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveRuntimePlan(env = process.env) {
|
function resolveRuntimePlan(env = process.env) {
|
||||||
@@ -757,6 +757,7 @@ module.exports = {
|
|||||||
resolveBackendExecutable,
|
resolveBackendExecutable,
|
||||||
schedulePiLabWarmup,
|
schedulePiLabWarmup,
|
||||||
shouldStartBackend,
|
shouldStartBackend,
|
||||||
|
shouldStartOpencode,
|
||||||
stopStaleMnoteWebCargoProcesses,
|
stopStaleMnoteWebCargoProcesses,
|
||||||
terminatePid,
|
terminatePid,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const {
|
|||||||
isPortFree,
|
isPortFree,
|
||||||
resolveRuntimePlan,
|
resolveRuntimePlan,
|
||||||
shouldStartBackend,
|
shouldStartBackend,
|
||||||
|
shouldStartOpencode,
|
||||||
stopStaleMnoteWebCargoProcesses,
|
stopStaleMnoteWebCargoProcesses,
|
||||||
} = require("./desktop-hot.js");
|
} = require("./desktop-hot.js");
|
||||||
|
|
||||||
@@ -188,6 +189,14 @@ test("默认跳过 FastAPI 后端,只有显式开启时才启动", () => {
|
|||||||
assert.equal(shouldStartBackend({ ENABLE_BACKEND: "1", SKIP_BACKEND: "1" }), false);
|
assert.equal(shouldStartBackend({ ENABLE_BACKEND: "1", SKIP_BACKEND: "1" }), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("默认跳过 opencode,只有显式开启时才启动", () => {
|
||||||
|
assert.equal(shouldStartOpencode({}), false);
|
||||||
|
assert.equal(shouldStartOpencode({ ENABLE_OPENCODE: "1" }), true);
|
||||||
|
assert.equal(shouldStartOpencode({ ENABLE_OPENCODE: "true" }), true);
|
||||||
|
assert.equal(shouldStartOpencode({ OPENCODE_CMD: "custom-opencode" }), true);
|
||||||
|
assert.equal(shouldStartOpencode({ ENABLE_OPENCODE: "1", SKIP_OPENCODE: "1" }), false);
|
||||||
|
});
|
||||||
|
|
||||||
test("opencode 默认命令使用 MNote 专用运行目录", () => {
|
test("opencode 默认命令使用 MNote 专用运行目录", () => {
|
||||||
const command = buildDefaultOpencodeCommand(18085);
|
const command = buildDefaultOpencodeCommand(18085);
|
||||||
assert.match(command, /\/mnt\/Data1T\/Mnote_data\/opencode\/runtime/);
|
assert.match(command, /\/mnt\/Data1T\/Mnote_data\/opencode\/runtime/);
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ const repoRoot = path.resolve(__dirname, '..');
|
|||||||
// Files to check
|
// Files to check
|
||||||
const files = {
|
const files = {
|
||||||
runtime: path.join(repoRoot, 'rust/crates/mnote-web/browser/sidebar-page-ai-pi-lab-runtime.js'),
|
runtime: path.join(repoRoot, 'rust/crates/mnote-web/browser/sidebar-page-ai-pi-lab-runtime.js'),
|
||||||
|
legacyPageAiRuntime: path.join(repoRoot, 'rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js'),
|
||||||
|
layout: path.join(repoRoot, 'rust/crates/mnote-web/src/ssr/pages/layout.rs'),
|
||||||
route: path.join(repoRoot, 'rust/crates/mnote-web/src/routes/page_ai_pi.rs'),
|
route: path.join(repoRoot, 'rust/crates/mnote-web/src/routes/page_ai_pi.rs'),
|
||||||
mod: path.join(repoRoot, 'rust/crates/mnote-web/src/routes/mod.rs'),
|
mod: path.join(repoRoot, 'rust/crates/mnote-web/src/routes/mod.rs'),
|
||||||
webShell: path.join(repoRoot, 'rust/crates/mnote-web/src/routes/web_shell.rs'),
|
webShell: path.join(repoRoot, 'rust/crates/mnote-web/src/routes/web_shell.rs'),
|
||||||
@@ -28,6 +30,8 @@ function readFile(p) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const runtime = readFile(files.runtime);
|
const runtime = readFile(files.runtime);
|
||||||
|
const legacyPageAiRuntime = readFile(files.legacyPageAiRuntime);
|
||||||
|
const layout = readFile(files.layout);
|
||||||
const route = readFile(files.route);
|
const route = readFile(files.route);
|
||||||
const routesMod = readFile(files.mod);
|
const routesMod = readFile(files.mod);
|
||||||
const webShell = readFile(files.webShell);
|
const webShell = readFile(files.webShell);
|
||||||
@@ -151,7 +155,11 @@ const checks = [
|
|||||||
['runtime preserves Omniroute/freefirst as selectable model', runtime.includes("'freefirst'") && runtime.includes('modelControlOptions')],
|
['runtime preserves Omniroute/freefirst as selectable model', runtime.includes("'freefirst'") && runtime.includes('modelControlOptions')],
|
||||||
['runtime labels freefirst option as omniroute/freefirst', runtime.includes("name: DEFAULT_MODEL_PROVIDER + '/freefirst'")],
|
['runtime labels freefirst option as omniroute/freefirst', runtime.includes("name: DEFAULT_MODEL_PROVIDER + '/freefirst'")],
|
||||||
['runtime has Pi Lab floating launcher', runtime.includes('data-page-ai-pi-lab-launcher')],
|
['runtime has Pi Lab floating launcher', runtime.includes('data-page-ai-pi-lab-launcher')],
|
||||||
|
['runtime reuses the existing floating AI slot for Pi Lab launcher', runtime.includes('[data-mnote-action="open-page-ai-pi-lab"], [data-testid="wolai-floating-ai"]')],
|
||||||
['runtime hides Pi Lab launcher while drawer is active', runtime.includes('piLabLauncherEl.hidden = !!piLabState.active')],
|
['runtime hides Pi Lab launcher while drawer is active', runtime.includes('piLabLauncherEl.hidden = !!piLabState.active')],
|
||||||
|
['layout maps old floating AI slot to Pi Lab', layout.includes('data-testid="wolai-floating-ai"') && layout.includes('data-mnote-action="open-page-ai-pi-lab"') && layout.includes('{"\\u{03c0}"}')],
|
||||||
|
['layout no longer exposes legacy Page AI/OpenHub action from floating button', !layout.includes('data-mnote-action="open-page-ai"><span')],
|
||||||
|
['legacy Page AI opencode host requires explicit debug opt-in', legacyPageAiRuntime.includes("getItem('mnote.page_ai.opencode_host') === '1'") && !legacyPageAiRuntime.includes("getItem('mnote.page_ai.opencode_host') !== '0' : true")],
|
||||||
['runtime documents pi-web-ui evidence', runtime.includes('@earendil-works/pi-web-ui@0.75.3')],
|
['runtime documents pi-web-ui evidence', runtime.includes('@earendil-works/pi-web-ui@0.75.3')],
|
||||||
['runtime uses MNote-native adapter boundary', runtime.includes('MNote-native adapter')],
|
['runtime uses MNote-native adapter boundary', runtime.includes('MNote-native adapter')],
|
||||||
|
|
||||||
|
|||||||
@@ -296,64 +296,23 @@ async function main() {
|
|||||||
|
|
||||||
const aiButton = page.locator('[data-testid="wolai-floating-ai"]').first();
|
const aiButton = page.locator('[data-testid="wolai-floating-ai"]').first();
|
||||||
await aiButton.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
await aiButton.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
||||||
|
const floatingAiText = ((await aiButton.textContent()) || "").trim();
|
||||||
|
assert(floatingAiText === "π", `浮动 AI 入口应退役旧 OpenHub 前端并显示 Pi Lab π: ${floatingAiText}`);
|
||||||
await aiButton.click({ timeout: UI_TIMEOUT_MS });
|
await aiButton.click({ timeout: UI_TIMEOUT_MS });
|
||||||
const aiDrawer = page.locator('[data-testid="wolai-page-ai-drawer"]').first();
|
const piDrawer = page.locator('[data-page-ai-pi-lab="drawer"]').first();
|
||||||
await aiDrawer.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
await piDrawer.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
||||||
const aiText = (await aiDrawer.textContent()) || "";
|
assert(await page.locator("[data-page-ai-pi-lab-input]").first().isVisible(), "Pi Lab drawer 必须显示输入框");
|
||||||
assert(aiText.includes("Hermes"), `Page AI drawer 必须显示 Hermes 入口: ${aiText}`);
|
const retiredFrontend = await page.evaluate(() => ({
|
||||||
assert(await page.locator("[data-page-ai-input]").first().isVisible(), "Page AI drawer 必须显示输入框");
|
legacyDrawerOpen: Boolean(document.querySelector('[data-testid="wolai-page-ai-drawer"]:not([hidden])')),
|
||||||
screenshots.pageAi = await saveScreenshot(page, "02-page-ai");
|
opencodeHost: Boolean(document.querySelector('[data-page-ai-opencode-host="true"]')),
|
||||||
await closePageAiIfOpen(page);
|
opencodeIframe: Boolean(document.querySelector('[data-page-ai-opencode-iframe]')),
|
||||||
await page.waitForFunction(
|
}));
|
||||||
() => {
|
assert(retiredFrontend.legacyDrawerOpen === false, "浮动 Pi 入口不能打开旧 Page AI/OpenHub drawer");
|
||||||
const drawer = document.querySelector('[data-testid="wolai-page-ai-drawer"]');
|
assert(retiredFrontend.opencodeHost === false, "浮动 Pi 入口不能打开 opencode/OpenHub host 前端");
|
||||||
const trigger = document.querySelector('[data-testid="wolai-floating-ai"]');
|
assert(retiredFrontend.opencodeIframe === false, "浮动 Pi 入口不能保留 opencode/OpenHub iframe");
|
||||||
return drawer instanceof HTMLElement
|
screenshots.pageAi = await saveScreenshot(page, "02-pi-lab");
|
||||||
&& drawer.hidden === true
|
await page.locator("[data-page-ai-pi-lab-close]").click({ timeout: UI_TIMEOUT_MS });
|
||||||
&& trigger instanceof HTMLElement
|
await piDrawer.waitFor({ state: "hidden", timeout: UI_TIMEOUT_MS });
|
||||||
&& trigger.getAttribute("data-state") === "closed"
|
|
||||||
&& trigger.getAttribute("aria-expanded") === "false";
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
{ timeout: UI_TIMEOUT_MS },
|
|
||||||
);
|
|
||||||
|
|
||||||
await aiButton.click({ timeout: UI_TIMEOUT_MS });
|
|
||||||
await aiDrawer.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
|
||||||
await page.locator("[data-page-ai-input]").fill("Task490 stop smoke", { timeout: UI_TIMEOUT_MS });
|
|
||||||
await page.locator('[data-page-ai-action="send"]').click({ timeout: UI_TIMEOUT_MS });
|
|
||||||
await page.waitForFunction(
|
|
||||||
() => document.documentElement.getAttribute("data-mnote-page-ai-run-status") === "running"
|
|
||||||
&& document.documentElement.getAttribute("data-mnote-page-ai-run-id") === "run_task490_stop",
|
|
||||||
null,
|
|
||||||
{ timeout: UI_TIMEOUT_MS },
|
|
||||||
);
|
|
||||||
const stopButton = page.locator('.wolai-page-ai-stop[data-page-ai-action="stop-run"]').first();
|
|
||||||
await stopButton.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
|
||||||
await page.waitForFunction(
|
|
||||||
() => {
|
|
||||||
const stop = document.querySelector('.wolai-page-ai-stop[data-page-ai-action="stop-run"]');
|
|
||||||
return stop instanceof HTMLButtonElement && stop.disabled === false && stop.getAttribute("aria-disabled") === "false";
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
{ timeout: UI_TIMEOUT_MS },
|
|
||||||
);
|
|
||||||
await stopButton.click({ timeout: UI_TIMEOUT_MS });
|
|
||||||
await page.waitForFunction(
|
|
||||||
() => document.documentElement.getAttribute("data-mnote-page-ai-run-status") === "aborted",
|
|
||||||
null,
|
|
||||||
{ timeout: UI_TIMEOUT_MS },
|
|
||||||
);
|
|
||||||
await page.locator('[data-page-ai-conversation]').getByText("已请求停止当前 AI run。").waitFor({
|
|
||||||
state: "visible",
|
|
||||||
timeout: UI_TIMEOUT_MS,
|
|
||||||
});
|
|
||||||
assert(capturedPageAi.runs.length === 1, "Page AI stop smoke 应先发起一个 run");
|
|
||||||
assert(capturedPageAi.aborts.length === 1, "点击停止应调用 abort API 一次");
|
|
||||||
const abortBody = JSON.parse(capturedPageAi.aborts[0] || "{}");
|
|
||||||
assert(abortBody.reason === "page_ai_user_stop", "abort API 应记录用户停止原因");
|
|
||||||
screenshots.pageAiStopped = await saveScreenshot(page, "02b-page-ai-stopped");
|
|
||||||
await closePageAiIfOpen(page);
|
|
||||||
|
|
||||||
await setBlockHandleFixture(page, UI_TIMEOUT_MS);
|
await setBlockHandleFixture(page, UI_TIMEOUT_MS);
|
||||||
await editor.click({ timeout: UI_TIMEOUT_MS });
|
await editor.click({ timeout: UI_TIMEOUT_MS });
|
||||||
|
|||||||
Reference in New Issue
Block a user