170 lines
6.2 KiB
JavaScript
170 lines
6.2 KiB
JavaScript
#!/usr/bin/env node
|
|
"use strict";
|
|
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
|
|
const repoRoot = path.resolve(__dirname, "..");
|
|
const openHubRoot =
|
|
process.env.OPENHUB_RESEARCH_ROOT || "/mnt/Data1T/tmp/mnote-openhub-research/OpenHub";
|
|
|
|
const files = {
|
|
chatInput: path.join(openHubRoot, "smart-query-frontend/src/components/ChatInput.jsx"),
|
|
smartQueryPage: path.join(openHubRoot, "smart-query-frontend/src/pages/SmartQueryPage.jsx"),
|
|
api: path.join(openHubRoot, "smart-query-frontend/src/services/api.js"),
|
|
openHubFrontendSrc: path.join(openHubRoot, "smart-query-frontend/src"),
|
|
mnoteOpenHubRoute: path.join(repoRoot, "rust/crates/mnote-web/src/routes/page_ai_openhub.rs"),
|
|
};
|
|
|
|
function read(file) {
|
|
return fs.readFileSync(file, "utf8");
|
|
}
|
|
|
|
function walkFiles(dir, result = []) {
|
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
const fullPath = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
walkFiles(fullPath, result);
|
|
} else {
|
|
result.push(fullPath);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function extractBetween(source, startNeedle, endNeedle) {
|
|
const start = source.indexOf(startNeedle);
|
|
if (start < 0) return "";
|
|
const end = source.indexOf(endNeedle, start);
|
|
return source.slice(start, end < 0 ? undefined : end);
|
|
}
|
|
|
|
function check(failures, name, passed, detail = "") {
|
|
if (!passed) {
|
|
failures.push(detail ? `${name}: ${detail}` : name);
|
|
}
|
|
}
|
|
|
|
const chatInput = read(files.chatInput);
|
|
const smartQueryPage = read(files.smartQueryPage);
|
|
const api = read(files.api);
|
|
const controlRow = extractBetween(chatInput, "<Segmented", "<TextArea");
|
|
const handleMNoteSend = extractBetween(chatInput, "const handleSendWithMNoteContext", "useEffect(() =>");
|
|
const handleSend = extractBetween(smartQueryPage, "const handleSend = async", "const handleKeyPress");
|
|
|
|
const legacyFloatingSelectors = [
|
|
"mnote-openhub-context-bar",
|
|
"mnote-openhub-native-context",
|
|
"data-mnote-openhub-ai-quick-actions",
|
|
"mnote_openhub_bridge_markup",
|
|
"insertAddress(",
|
|
];
|
|
|
|
const frontendLegacyHits = [];
|
|
for (const file of walkFiles(files.openHubFrontendSrc)) {
|
|
const rel = path.relative(openHubRoot, file);
|
|
const source = read(file);
|
|
for (const selector of legacyFloatingSelectors) {
|
|
if (source.includes(selector)) {
|
|
frontendLegacyHits.push(`${rel} -> ${selector}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
const mnoteRoute = fs.existsSync(files.mnoteOpenHubRoute) ? read(files.mnoteOpenHubRoute) : "";
|
|
const mnoteRouteLegacyHits = legacyFloatingSelectors.filter((selector) =>
|
|
mnoteRoute.includes(selector)
|
|
);
|
|
|
|
const failures = [];
|
|
|
|
check(
|
|
failures,
|
|
"ChatInput owns two native MNote context buttons",
|
|
chatInput.includes("isMNoteOpenHubEmbed()") &&
|
|
chatInput.includes("requestMNoteActiveTabAddress") &&
|
|
chatInput.includes("data-mnote-openhub-current-tab-toggle") &&
|
|
chatInput.includes("data-mnote-openhub-current-folder-toggle"),
|
|
"missing embed gate, context request helper, or native button data attributes"
|
|
);
|
|
|
|
check(
|
|
failures,
|
|
"MNote context buttons are in the controls row beside agent/model controls",
|
|
controlRow.includes("<Segmented") &&
|
|
controlRow.includes("<ModelSelect") &&
|
|
controlRow.includes("data-mnote-openhub-current-tab-toggle") &&
|
|
controlRow.includes("data-mnote-openhub-current-folder-toggle") &&
|
|
controlRow.indexOf("<Segmented") < controlRow.indexOf("data-mnote-openhub-current-tab-toggle") &&
|
|
controlRow.indexOf("<ModelSelect") < controlRow.indexOf("data-mnote-openhub-current-folder-toggle"),
|
|
"native buttons must be before TextArea and in the same row as agent segmented/model select"
|
|
);
|
|
|
|
check(
|
|
failures,
|
|
"Icon-only buttons use tooltips and aria labels for current page/tab and folder",
|
|
/Tooltip\s+title=["{][^"'}]*(当前(?:页面|激活\s*Tab|Tab)|current\s*(?:page|tab))/i.test(controlRow) &&
|
|
/Tooltip\s+title=["{][^"'}]*(当前(?:文件夹|激活\s*Tab\s*的文件夹)|current\s*folder|folder)/i.test(controlRow) &&
|
|
/aria-label="当前页面"/.test(controlRow) &&
|
|
/aria-label="文件夹"/.test(controlRow) &&
|
|
/icon=\{<FileTextOutlined\s*\/>\}/.test(controlRow) &&
|
|
/icon=\{<FolderOpenOutlined\s*\/>\}/.test(controlRow) &&
|
|
!/>当前页面<\/Button>/.test(controlRow) &&
|
|
!/>文件夹<\/Button>/.test(controlRow),
|
|
"expected two icon-only buttons with distinct tooltip and aria labels"
|
|
);
|
|
|
|
check(
|
|
failures,
|
|
"ChatInput sends mnote_context as hidden metadata instead of writing URL into textarea question",
|
|
/handleSend\(undefined,\s*context\s*\?\s*\{/.test(handleMNoteSend) &&
|
|
handleMNoteSend.includes("kind: mnoteContextMode") &&
|
|
handleMNoteSend.includes("value: context.value") &&
|
|
!/setQuestion\s*\(\s*context\./.test(handleMNoteSend) &&
|
|
!/setQuestion\s*\(\s*mnoteContext/.test(handleMNoteSend) &&
|
|
!/question\s*=\s*context\.value/.test(handleMNoteSend),
|
|
"context must flow through handleSend second argument and must not mutate the visible question value"
|
|
);
|
|
|
|
check(
|
|
failures,
|
|
"SmartQueryPage accepts mnote_context and forwards it to queryDataService.queryDataStream",
|
|
/const\s+handleSend\s*=\s*async\s*\(\s*overrideQuestion\s*,\s*mnoteContext\s*=\s*null\s*\)/.test(
|
|
smartQueryPage
|
|
) &&
|
|
handleSend.includes("queryDataService.queryDataStream(") &&
|
|
/abortControllerRef\.current\.signal\s*,\s*mnoteContext/.test(handleSend),
|
|
"handleSend must accept mnoteContext and pass it through the stream send call"
|
|
);
|
|
|
|
check(
|
|
failures,
|
|
"OpenHub send body carries hidden mnote_context",
|
|
/queryDataStream:\s*async\s*\([^)]*mnoteContext\s*=\s*null/.test(api) &&
|
|
api.includes("requestBody.mnote_context = mnoteContext") &&
|
|
!/question\s*:\s*.*mnoteContext/.test(api),
|
|
"api.js must put mnoteContext into requestBody.mnote_context, not append it to question"
|
|
);
|
|
|
|
check(
|
|
failures,
|
|
"OpenHub frontend no longer depends on legacy MNote floating button selectors",
|
|
frontendLegacyHits.length === 0,
|
|
frontendLegacyHits.join("; ")
|
|
);
|
|
|
|
check(
|
|
failures,
|
|
"MNote OpenHub route no longer injects legacy floating context buttons",
|
|
mnoteRouteLegacyHits.length === 0,
|
|
mnoteRouteLegacyHits.join("; ")
|
|
);
|
|
|
|
if (failures.length) {
|
|
console.error("OpenHub native MNote context source smoke failed:");
|
|
for (const failure of failures) console.error(`- ${failure}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log("OpenHub native MNote context source smoke passed.");
|